FFHS DAS Data Science
Semesterarbeit FS20
Iwan Imsand

Statistische Datenanalyse, StatDa
Nach der ersten Präsenz

Deskriptive Analyse

In diesem Kapitel wird eine deskriptive Analyse für den gewählten Datensatz durchgeführt.

Zusammenfassung

TODO

Daten

Quelle der Rohdaten

Folgende Quellen wurden benutzt:

Die Datenbeschaffung, Bereinigung und Zusammenführung wurde in den Notebooks 0_*.ipynb durchgeführt.

Daten für die Analysen

Als Grundlage für alle nachfolgenden Analysen, dienen die folgenden Daten:

Dateiname Beschreibung
samples_5000_201910-citibike-tripweather-data.parquet Enthält 5000 zufällig gewählte Stichproben aus dem Monat Oktober des Jahres 2019.
summary-daily-subscribers_only-citibike-tripweather.parquet Enthält eine aggregierte Zusammenfassung pro Tag über alle Jahre. Es wurden nur Jahresmitglieder berücksichtigt!
summary-daily-subscribers_only-gender_grouped-citibike-tripweather.parquet Enthält eine nach Geschlecht gruppierte und aggregierte Zusammenfassung pro Tag über alle Jahre. Es wurden nur Jahresmitglieder berücksichtigt!

Alle Dateien befinden sich im Pfad ./../data/citibike/tripdata/.

In [1]:
import os
import pandas as pd
import numpy as np
import math

import matplotlib
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import matplotlib.dates as mdates
from matplotlib.lines import Line2D
import seaborn as sns

from datetime import datetime
from dateutil import tz

import sidetable
from plotnine import *
In [2]:
%matplotlib inline
In [3]:
# Needed for correct time plots
matplotlib.rcParams['timezone'] = 'US/Eastern'
In [4]:
pd.options.display.float_format = '{:.5f}'.format
In [5]:
path = './../data/citibike/tripdata'
In [6]:
SEED=4242123 # Needed when using sample
In [7]:
def show_hist(data, xlabel, ylabel, title, nbins=0, log=False, figsize=(15,2)):
    '''
    Shows a histogram of data with some other informations.
    '''

    fig = plt.figure(figsize=figsize)

    if nbins == 0:
        if data.count() <= 100:
            nbins = int(math.sqrt(data.count()))
        elif data.count() <= 1000:
            nbins = 15
        else:
            nbins = 20        

    ax = plt.subplot()
    n, bins, patches = ax.hist(data, bins=nbins, log=log)
    ax.set_xlabel(xlabel)
    if log:
        ax.set_ylabel(ylabel + '(log)')
    else:
        ax.set_ylabel(ylabel + '(lin)')
    ax.set_title(title)

    ax.set_axisbelow(False)
    ax.yaxis.grid(linestyle='dashed')
    ax.xaxis.grid(linestyle='dashed')

    plt.show()

    print('Info Histogram: [ Klassen: {}, Breite: {:.5f}, Skew: {:.5f}, Kurt: {:.5f} ]'.format(
        len(n),
        np.diff(bins)[0],
        data.skew(),
        data.kurt())
    )
    
    return n, bins, patches
In [8]:
def show_box(data, xlabel, title, ylabel='', vert=False, labels=[''], log=False, figsize=(15,2)):
    '''
    Shows a boxplot of data.
    '''
    fig = plt.figure(figsize=figsize)

    ax = plt.subplot()
    boxplot = ax.boxplot(data, vert=vert, showmeans=True, labels=labels, widths=0.7)

    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.set_title(title)

    if log:
        ax.set_xscale('log')
    
    ax.set_axisbelow(True)
    #ax.yaxis.grid(linestyle='dashed')
    ax.xaxis.grid(linestyle='dashed')
    
    #plt.tight_layout()
    plt.show()
    
    return boxplot

Analyse samples_5000_201910-citibike-tripweather-data.parquet

Merkmale

In [9]:
df_oct2019 = pd.read_parquet(os.path.join(path, 'samples_5000_201910-citibike-tripweather-data.parquet'))
In [10]:
df_oct2019.head().T
Out[10]:
811188 623554 1722948 1493393 909432
dt_utc 2019-10-12 16:20:30+00:00 2019-10-10 01:07:17+00:00 2019-10-25 21:17:12+00:00 2019-10-22 21:36:53+00:00 2019-10-13 20:59:23+00:00
Trip Duration 357 299 552 220 2063
Start Time 2019-10-12 12:20:30-04:00 2019-10-09 21:07:17-04:00 2019-10-25 17:17:12-04:00 2019-10-22 17:36:53-04:00 2019-10-13 16:59:23-04:00
Stop Time 2019-10-12 12:26:27-04:00 2019-10-09 21:12:16-04:00 2019-10-25 17:26:25-04:00 2019-10-22 17:40:34-04:00 2019-10-13 17:33:46-04:00
Start Station ID 3411 3102 3233 296 3777
Start Station Name Bond St & Bergen St Driggs Ave & Lorimer St E 48 St & 5 Ave Division St & Bowery Stockholm St & Wilson Ave
Start Station Latitude 40.68497 40.72179 40.75725 40.71413 40.69930
Start Station Longitude -73.98621 -73.95042 -73.97806 -73.99705 -73.92304
End Station ID 83 3094 486 307 3346
End Station Name Atlantic Ave & Fort Greene Pl Graham Ave & Withers St Broadway & W 29 St Canal St & Rutgers St Berkeley Pl & 7 Ave
End Station Latitude 40.68383 40.71698 40.74620 40.71427 40.67515
End Station Longitude -73.97632 -73.94486 -73.98856 -73.98990 -73.97523
Bike ID 17412 34154 20074 28029 40779
User Type Subscriber Subscriber Subscriber Subscriber Subscriber
Birth Year 1997 1988 1980 1962 1989
Gender 1 2 1 1 1
Linear Distance 0.84306 0.71088 1.51337 0.60254 5.15542
Age 2020 23 32 40 58 31
temp 15.38000 12.22000 17.38000 15.35000 18.04000
pressure 1015 1024 1022 1012 1017
humidity 69 73 73 92 62
wind_speed 3.05000 8.20000 5.04000 9.64000 3.66000
rain_1h 0.00000 0.25000 0.00000 0.39000 0.00000
snow_1h 0.00000 0.00000 0.00000 0.00000 0.00000
clouds_all 60 0 100 100 100
weather_id 803 500 804 500 804
weather_main Clouds Rain Clouds Rain Clouds
weather_description broken clouds light rain overcast clouds light rain overcast clouds

Die Merkmale die in der Datei samples_5000_201910-citibike-tripweather-data.parquet enthalten sind, werden in folgender Tabelle beschrieben.

Statistische Einheit Merkmal Merkmalsausprägung / Beispiel Skalenniveau Kontinuität Beschreibung
Trip dt_utc 2019-10-01 400:00:05+00:00 Intervall stetig Zeitpunkt an welchem der Trip gestartet wurde in 'Koordinierter Weltzeit' (UTC)
Trip Trip Duration 100 Sekunden Verhältnis stetig Dauer des Trips in Sekunden
Trip Start Time 2019-01-01 00:01:05-04:00 Intervall stetig Zeitpunkt an welchem der Trip gestartet wurde mit Zeitzone US/Eastern
Trip Stop Time 2019-01-01 00:07:07-04:00 Intervall stetig Zeitpunkt an welchem der Trip beendet wurde mit Zeitzone US/Eastern
Trip Linear Distance 1.55503 Verhältnis stetig Distanz der Luftlinie zwischen Start und Stop Station in km (Berechnet mit haversine)
Station Start Station ID 3160 Nominal diskret Eindeutige Identifikation der Station an welcher der Trip gestartet wurde
Station Start Station Name Central Park West & W 76 St Nominal diskret Name der Startstation
Station Start Station Latitude 40.778968 Intervall stetig Breitengrad der Startstation
Station Start Station Longitude -73.973747 Intervall stetig Längengrad der Startstation
Station End Station ID 3283 Nominal diskret Eindeutige Identifikation der der Station an welcher der Trip beendet wurde
Station End Station Name W 89 St & Columbus Ave Nominal diskret Name der Endstation
Station End Station Latitude 40.788221 Intervall stetig Breitengrad der Endstation
Station End Station Longitude -74.00597 Intervall stetig Längengrad der Endstation
Bike Bike ID 15839 Nominal diskret Eindeutige Identifikation des Bikes
User User Type Subscriber Nominal diskret Benutzertyp: Customers=24-hour pass oder 3-day pass; Subscribers=Annual Member
User Birth Year 1971 Intervall diskret Geburtsjahr des Benutzers
User Gender 1 Nominal diskret Geschlecht des Benutzers: 0=unknown; 1=male; 2=female
User Age 2020 28 Verhältnis diskret Alter des Benutzers im Jahr 2020 in Jahren
Temperature temp 17.52 Intervall stetig Temperatur in Grad Celsius
Air pressure 1023 Intervall stetig Luftdruck (auf Meereshöhe), hPa
Air humidity 75 Intervall stetig Luftfeuchtigkeit in %
Wind wind_speed 5.27 Verhältnis stetig Windgeschwindigkeit in m/s
Rainfall rain_1h 0.25 Verhältnis stetig Regenmenge der letzten Stunde in mm
Snowfall snow_1h 0.00 Verhältnis stetig Schneemenge der letzten Stunde in mm
Clouds clouds_all 100 Intervall stetig Bewölkung in %
Weather weather_id 500 Nominal diskret Code für die Wetterbedingung (Aufgelistet unter https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2)
Weather weather_main Rain Nominal diskret Gruppe der Wetterbedingung (z.B. Rain,Clear,Clouds, für weitere: https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2)
Weather weather_description light rain Nominal diskret Beschreibung der Wetterbedingung (z.B. light rain, sky is clear, few clouds, für weitere https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2)

Umrechnung Einheiten

Damit eine Interpretation einiger Merkmale in der Analyse einfacher wird, werden zusätzliche Spalten in anderen Einheiten hinzugefügt.

In [11]:
df_oct2019.insert(loc=2, column='Trip Duration (min)', value=df_oct2019['Trip Duration'] / 60)
df_oct2019.insert(loc=3, column='Trip Duration (h)', value=df_oct2019['Trip Duration'] / 60 / 60)
df_oct2019.insert(loc=4, column='Trip Duration (d)', value=df_oct2019['Trip Duration'] / 60 / 60 / 24)

Folgende Spalten wurden ergänzt:

In [12]:
[col for col in df_oct2019.columns if col.endswith(')') ]
Out[12]:
['Trip Duration (min)', 'Trip Duration (h)', 'Trip Duration (d)']

Trips im Oktober 2019

Untersuchungen zur Anzahl der Trips im Oktober 2019.

In [13]:
df_oct2019_idx = df_oct2019.set_index(pd.DatetimeIndex(data=df_oct2019['Start Time'].dt.date, name='Date', tz=tz.gettz('US/Eastern')))
In [14]:
df_oct2019_grouped = df_oct2019_idx.groupby(by=['Date'])[['Trip Duration']].agg('count').rename({'Trip Duration': 'Trip count'}, axis=1)
df_oct2019_grouped = df_oct2019_grouped.resample('1D').ffill()
In [15]:
df_oct2019_grouped.describe()
Out[15]:
Trip count
count 31.00000
mean 161.29032
std 43.33143
min 60.00000
25% 131.50000
50% 170.00000
75% 199.50000
max 212.00000
In [16]:
days_in_mean_pm_std = df_oct2019_grouped[
    (df_oct2019_grouped > (df_oct2019_grouped.mean() - df_oct2019_grouped.std())) & 
    (df_oct2019_grouped < (df_oct2019_grouped.mean() + df_oct2019_grouped.std()))
].count()
days_in_mean_pm_std_percent = days_in_mean_pm_std / df_oct2019_grouped.count()
print('Anzahl der Daten, die im Bereich mean+-std liegen: {:.2f}% ({} Tage)'.format(days_in_mean_pm_std_percent[0]*100, days_in_mean_pm_std[0]))
Anzahl der Daten, die im Bereich mean+-std liegen: 67.74% (21 Tage)

Folgendes ist aus den Zahlen herauszulesen:

  • Es gibt 31 Einträge, dies weil der Monat Oktober 31 Tage hat.
  • Durchschnittlich wurden im Oktober 2019 jeden Tag 161.3 Trips gefahren.
  • 68% der Daten liegen im Bereich $161.3\pm43.3$, d.h. an 21 Tagen liegt die Anzahl der Trips zwischen $161.3\pm43.3$.
  • 60 Trips ist die tiefste Anzahl und 212 Trips die höchste Anzahl die an einem Tag gefahren wurden.
  • Der Median liegt bei 170 Trips.
  • An 75% der Tage, wurden über 200 Trips gefahren, an 25% der Tage weniger als 132 Trips.
In [17]:
n, bins, patches = show_hist(df_oct2019_grouped['Trip count'], 'Anzahl Trips', 'Anzahl (linear)', 'Trips im Oktober 2019')
Info Histogram: [ Klassen: 5, Breite: 30.40000, Skew: -0.87613, Kurt: -0.07260 ]
In [18]:
print('Grenzen: {}, Anzahl: {}'.format(bins, n))
Grenzen: [ 60.   90.4 120.8 151.2 181.6 212. ], Anzahl: [ 2.  4.  4.  8. 13.]

Im Histogramm ist ersichtlich, dass es Tage gab, an welchen nur 60-90 Trips gefahren wurden, dies sieht man am Balken ganz rechts. Es gab nur 2 Tage im Monat Oktober, an welchen die Anzahl der Trips unter 91 fiel (die Obergrenze der ersten Klasse liegt bei 90.4, daher sind 90 Trips noch in der ersten Klasse enthalten). An 13 Tagen wurden zwischen 182 und 212 Trips gefahren, was an dem Balken ganz rechts abgelesen werden kann.

Auch zu erwähnen sind die Zahlen Skew und Kurt:

  • Skew: Die Werte sind mit -0.87613 moderat schief, die Häufung befindet sich rechts.
  • Kurt: Mit -0.07260 haben wir eine mittlere, normale Wölbung.
In [19]:
boxplot = show_box(df_oct2019_grouped['Trip count'], 'Anzahl Trips', 'Trips im Oktober 2019', labels=['Trip count'])

Der Boxplot zeigt das gleiche Bild das schon oben alles erkannt wurde, sofort ersichtlich ist hier aber (die genauen Zahlen wurden von oben abgelesen):

  • 50% der Daten liegen zwischen 131.5 und 199.5 Trips.
  • der Median (rote Linie), liegt bei 170 Trips und der Durchschnitt bei 161.3 Trips.
  • das Minimium liegt bei 60 Trips und das Maximum bei 212 Trips.
  • in diesen daten gibt es keine Ausreisser, d.h. alle Beobachtungen sind innerhalb der unnauffälligen Streuung. (1.5-fache Quartildistanz)

Es wird noch ein Balkendiagramm mit der Anzahl Trips über den ganzen Monat erstellt. Auch wird das Wochenende farblich markiert.

In [20]:
df_oct2019_grouped_subscriber = df_oct2019_idx[df_oct2019_idx['User Type'] == 'Subscriber'].groupby(by=['Date'])[['Trip Duration']].agg('count').rename({'Trip Duration': 'Trip count'}, axis=1)
df_oct2019_grouped_subscriber = df_oct2019_grouped_subscriber.resample('1D').ffill()
df_oct2019_grouped_customer = df_oct2019_idx[df_oct2019_idx['User Type'] == 'Customer'].groupby(by=['Date'])[['Trip Duration']].agg('count').rename({'Trip Duration': 'Trip count'}, axis=1)
df_oct2019_grouped_customer = df_oct2019_grouped_customer.resample('1D').ffill()
In [21]:
fig = plt.figure(figsize=(20, 5))

ax = fig.add_subplot()
_ = df_oct2019_grouped.plot(ax=ax, y='Trip count', kind='bar', color=df_oct2019_grouped.index.map(lambda date: 'darkblue' if date.weekday() in [5, 6] else 'royalblue'))

ax.set_xlabel('Tag')
ax.set_ylabel('Anzahl Trips')
ax.set_title('Trips im Oktober 2019')

ax.yaxis.grid(linestyle='dashed')

ax.set_xticklabels(df_oct2019_grouped.index.day)

custom_lines = [Line2D([0], [0], color='royalblue', lw=4),
                Line2D([0], [0], color='darkblue', lw=4)]
ax.legend(custom_lines, ['Workday', 'Weekend'])

#plt.tight_layout()
plt.show()

Es gibt am 20. Oktober und am 27. Oktober einen Einbruch der Anzahl Trips, d.h. es sind an beiden Tagen knapp über 50 Trips gemacht worden. An den anderen Tagen sind immer über 100 Trips vorhanden. Die zwei Tage, mit den wenigsten Trips, sind Sonntage. Es fällt auf, dass immer bei einem Arbeitstag die Anzhal der Trips knapp über 100 ist, an anderen Tagen aber höher.

Eine weitere Grafik soll die Anzahl der Trips gruppiert nach User Type zeigen.

In [22]:
fig = plt.figure(figsize=(20, 5))

ax = fig.add_subplot()
ax.plot_date(df_oct2019_grouped.index, df_oct2019_grouped['Trip count'], marker='o', ls='-', label='Gesamt')
ax.plot_date(df_oct2019_grouped_subscriber.index, df_oct2019_grouped_subscriber['Trip count'], marker='o', ls='-', label='Subscriber')
ax.plot_date(df_oct2019_grouped_customer.index, df_oct2019_grouped_customer['Trip count'], marker='o', ls='-', label='Customer')
ax.plot

ax.set_xlabel('Tag')
ax.set_ylabel('Anzahl Trips')
ax.set_title('Trips im Oktober 2019')

ax.yaxis.grid(linestyle='dashed')
ax.xaxis.grid(linestyle='dashed')

ax.xaxis.set_major_locator(mdates.DayLocator(interval=1))
ax.xaxis.set_major_formatter(mdates.DateFormatter("%d"))

ax.set_ylim(0, 250)
ax.set_xlim([datetime(2019, 10, 1, 0, 0, 0, tzinfo=tz.gettz('US/Eastern')), datetime(2019, 10, 31, 0, 0, 0, tzinfo=tz.gettz('US/Eastern'))])

for i, label in enumerate(ax.get_xticklabels()):
    if df_oct2019_grouped.index[i].date().weekday() in [5,6]:
        label.set_color('red')
    
ax.legend()

#plt.tight_layout()
plt.show()

Hier sieht man, dass erheblich mehr Trips durch Subscriber gefahren werden als durch Customer. Die rot gekennzeichneten Tage in der x-Achse sind Wochenenden, also Samstage und Sonntage.

User Type und Gender

Ein paar Untersuchungen zu User Type und Gender.

In [23]:
df_oct2019[['User Type', 'Gender']].describe()
Out[23]:
User Type Gender
count 5000 5000
unique 2 3
top Subscriber 1
freq 4288 3359

Es ist ersichtlich, dass

  • wir in der Stichprobe 5000 Beobachtungen haben.
  • bei User Type 2 unterschiedliche Werte vorhanden sind (Customer und Subscriber), und Subscriber am meisten vorkommt, nämlich 4288mal.
  • bei Gender 3 unterschiedliche Werte vorhanden sind (0, 1, und 2 - unknown, male, female) und 1 (male) am meisten vorkommt, nämlich 3359mal.

Anzahl der Trips, gruppiert nach User Type.

In [24]:
trip_count_by_user_type = df_oct2019.groupby(by=['User Type'])['Trip Duration'].agg('count')
trip_count_by_user_type.name = 'Trip Counts'
trip_count_by_user_type
Out[24]:
User Type
Customer       712
Subscriber    4288
Name: Trip Counts, dtype: int64

712 Trips wurden von Customern gefahren, 4288 von Subscribern.

Anzahl der Trips, gruppiert nach User Type und Gender.

In [25]:
trip_count_by_user_type_and_gender = df_oct2019.groupby(by=['User Type', 'Gender'])['Trip Duration'].agg('count')
trip_count_by_user_type_and_gender.name = 'Trip Counts'
trip_count_by_user_type_and_gender
Out[25]:
User Type   Gender
Customer    0          320
            1          265
            2          127
Subscriber  0           75
            1         3094
            2         1119
Name: Trip Counts, dtype: int64

Es wurden

  • 320 Trips von Customern gefahren bei denen das Geschlecht nicht bekannt ist.
  • 265 Trips von männlichen Customern gefahren.
  • 127 Trips von weiblichen Customern gefahren.
  • 75 Trips von Subscribern gefahren, bei denen das Geschlecht nicht bekannt ist.
  • 3094 Trips von männlichen Subscribern gefahren.
  • 1119 Trips von weiblichen Subscribern gefahren.

Die Zahlen werden auch noch graphisch aufbereitet.

In [26]:
def func(pct, allvals):
    idx = np.argwhere(np.round(allvals / allvals.sum() * 100, 3).values == np.round(pct, 2))[0][0]
    absolute = int(allvals[idx])
    return "{:.1f}%\n({:d})".format(pct, absolute)

def func_inner(pct, allvals):
    idx = np.argwhere(np.round(allvals / allvals.sum() * 100, 3).values == np.round(pct, 2))[0][0]
    if idx <=2:
        pct_new = allvals[idx] / allvals[0:3].sum() * 100
        absolute = int(allvals[idx])
    else:
        pct_new = allvals[idx] / allvals[3:6].sum() * 100
        absolute = int(pct_new/100.*np.sum(allvals))
    return "{:.1f}% ({:d})".format(pct_new, absolute)

fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(30, 15))

size = 0.3
cmap = plt.get_cmap("tab20c")
outer_colors = cmap(np.arange(3)*4)
inner_colors = cmap(np.array([1, 2, 3, 5, 6, 7]))

outer_labels = ['Customer', 'Subscriber']
inner_labels = ['unknown', 'male', 'female', 'unknown', 'male', 'female']
inner_labels_abs = ['unknown Customers', 'male Customers', 'female Customers', 'unknown Subscribers', 'male Subscribers', 'female Subscribers']

ax[0].pie(trip_count_by_user_type, autopct=lambda pct: func(pct, trip_count_by_user_type), labels=outer_labels, radius=1, colors=outer_colors,
       wedgeprops=dict(width=size, edgecolor='w'), pctdistance=0.85, labeldistance=1.05)

ax[0].pie(trip_count_by_user_type_and_gender, autopct=lambda pct: func_inner(pct, trip_count_by_user_type_and_gender), labels=inner_labels, radius=1-size, colors=inner_colors,
       wedgeprops=dict(width=size, edgecolor='w'), pctdistance=0.78, labeldistance=0.39, rotatelabels=True)

ax[1].pie(trip_count_by_user_type_and_gender, autopct=lambda pct: func(pct, trip_count_by_user_type_and_gender), labels=inner_labels_abs, radius=1-size, colors=inner_colors,
       wedgeprops=dict(width=size, edgecolor='w'), pctdistance=0.78, labeldistance=1.05, rotatelabels=True)

ax[0].set(aspect="equal", title='Anteil `User Type` und `Gender`')
ax[1].set(aspect="equal", title='Anteil `User Type` und `Gender`')

#plt.tight_layout()
plt.show()

In der linken Grafik ist folgendes ersichtlich:

  • Die Trips werden mit 85.8% überwiegend von Subscribern gefahren. Die Customer sind mit 14.2% in der Unterzahl.
  • Hauptsächlich fahren Männer mit den Bikes. Von den Subscribern sind dies 72.2% Männer gegenüber 26.1% Frauen. Bei den Customern 37.2% Männer gegenüber 17.8% Frauen.
  • Das Geschlecht wird bei den Subscribern fast immer angegeben, nur 1.7% geben bei einem Trip kein Geschlecht an. Bei den Customern wird das Geschlecht grösstenteils nicht angegeben, der Anteil ist hier mit 44.9% am höchsten.

Die rechte Grafik stellt das innere Diagramm einfach nochmal dar, die Zahlen beziehen sich hier aber auf die gesamte Anzahl der Trips.

Trip Duration

Untersuchungen zur Dauer der Trips.

In [27]:
df_oct2019[[col for col in df_oct2019.columns if col.startswith('Trip Duration')]].describe(percentiles=[0.05, .25, .5, .75, .95])
Out[27]:
Trip Duration Trip Duration (min) Trip Duration (h) Trip Duration (d)
count 5000.00000 5000.00000 5000.00000 5000.00000
mean 922.72860 15.37881 0.25631 0.01068
std 5575.16818 92.91947 1.54866 0.06453
min 65.00000 1.08333 0.01806 0.00075
5% 182.95000 3.04917 0.05082 0.00212
25% 362.00000 6.03333 0.10056 0.00419
50% 606.00000 10.10000 0.16833 0.00701
75% 1021.25000 17.02083 0.28368 0.01182
95% 1984.05000 33.06750 0.55113 0.02296
max 377055.00000 6284.25000 104.73750 4.36406
In [28]:
trip_duration_in_mean_pm_std = df_oct2019[
    (df_oct2019['Trip Duration'] > (df_oct2019['Trip Duration'].mean() - df_oct2019['Trip Duration'].std())) & 
    (df_oct2019['Trip Duration'] < (df_oct2019['Trip Duration'].mean() + df_oct2019['Trip Duration'].std()))
]['Trip Duration'].count()
trip_duration_in_mean_pm_std_percent = trip_duration_in_mean_pm_std / df_oct2019['Trip Duration'].count()
print('Anzahl der Beobachtungen, die im Bereich mean+-std liegen: {:.2f}% ({} Trips)'.format(trip_duration_in_mean_pm_std_percent*100, trip_duration_in_mean_pm_std))
Anzahl der Beobachtungen, die im Bereich mean+-std liegen: 99.66% (4983 Trips)

Folgende Zahlen sind erwähnenswert:

  • Der Median liegt bei 10.1 Minuten.
  • Ein Trip dauerte durchschnittlich 15.4 Minuten.
  • Der längste Trip dauerte 4.4 Tage und der kürzeste 1.1 Minuten.
  • 5% der Trips dauerten mehr als 33.1 Minuten und 5% der Trips weniger als 3 Minuten.
  • Die Standardabweichung beträgt 92.92min und 99.66% (4983 Trips) der Beobachtungen befinden sich im Bereich $15.38\pm92.92 min$

Das der Durchschnitt 5.3 Minuten über dem Median liegt, ist wohl den ganz langen Trips, wie z.B. dem Ausreisser von 4.4 Tagen, zuzuschreiben.

In [29]:
_ = show_hist(df_oct2019['Trip Duration (min)'], 'min', 'Anzahl', 'Trip Duration im Oktober 2019')
Info Histogram: [ Klassen: 20, Breite: 314.15833, Skew: 62.24698, Kurt: 4156.34407 ]

Es scheint nur eine Klasse nahe bei null zu geben. Die Zahlen oben haben schon gezeigt das 95% der Werte unter 33.1 Minuten liegen.

Auch sind die Daten schief und wir haben eine starke Wölbung:

  • Skew: 62.24698 stark schief, die Häufung befindet sich links.
  • Kurt: Mit 4156.34407 haben wir eine sehr starke Wölbung.

Auch ein Boxplot zeigt, dass die Werte konzentriert in der Nähe von 0 liegen.

In [30]:
_ = show_box(df_oct2019['Trip Duration (min)'], 'min', 'Trip Duration im Oktober 2019', labels=['Trip Duration'])

Damit die Werte rechts auch ersichtlich werden, wird das Histogramm noch mit logarithmischer y-Achse erstellt.

In [31]:
_ = show_hist(df_oct2019['Trip Duration (min)'], 'min', 'Anzahl', 'Trip Duration im Oktober 2019', log=True)
Info Histogram: [ Klassen: 20, Breite: 314.15833, Skew: 62.24698, Kurt: 4156.34407 ]

Ab ca. 600 Minuten gibt quasi keine Klassen mehr. Da nur 5% der Werte (d.h. genau $5000 * 0.05 = 250$ Werte) über 33.1 Minuten liegen, wird für die Übersichtlichkeit noch ein Plot mit den Trips, die kürzer als 33.1 Minuten sind, erstellt.

In [32]:
df_oct_2019_trip_duration_under_33_1min = df_oct2019[df_oct2019['Trip Duration (min)'] < 33.1]
In [33]:
_ = show_hist(df_oct_2019_trip_duration_under_33_1min['Trip Duration (min)'], 'min', 'Anzahl', 'Trip Duration < 33.1min im Oktober 2019 ()')
Info Histogram: [ Klassen: 20, Breite: 1.60000, Skew: 0.96055, Kurt: 0.16901 ]

Das Histogramm sieht schon viel besser aus und die Ausreisser wurden entfernt.

Auch sind die Daten nun weniger schief, d.h. moderat schief und die Häufung befindet sich rechts, und wir haben nun auch eine mittlere Wölbung.

Auch der Boxplot zeigt nun ein besseres Bild der Beobachtungen.

In [34]:
_ = show_box(df_oct_2019_trip_duration_under_33_1min['Trip Duration (min)'], 'min', 'Trip Duration im Oktober 2019', labels=['Trip Duration'])

Und dazu auch noch die Tabelle mit einer Beschreibung der Werte.

In [35]:
df_oct_2019_trip_duration_under_33_1min[[col for col in df_oct2019.columns if col.startswith('Trip Duration')]].describe(percentiles=[0.05, .25, .5, .75, .95])
Out[35]:
Trip Duration Trip Duration (min) Trip Duration (h) Trip Duration (d)
count 4751.00000 4751.00000 4751.00000 4751.00000
mean 693.62345 11.56039 0.19267 0.00803
std 441.27930 7.35465 0.12258 0.00511
min 65.00000 1.08333 0.01806 0.00075
5% 179.00000 2.98333 0.04972 0.00207
25% 354.00000 5.90000 0.09833 0.00410
50% 575.00000 9.58333 0.15972 0.00666
75% 939.00000 15.65000 0.26083 0.01087
95% 1635.50000 27.25833 0.45431 0.01893
max 1985.00000 33.08333 0.55139 0.02297
In [36]:
trip_duration_in_mean_pm_std_33_1min = df_oct_2019_trip_duration_under_33_1min[
    (df_oct_2019_trip_duration_under_33_1min['Trip Duration'] > (df_oct_2019_trip_duration_under_33_1min['Trip Duration'].mean() - df_oct_2019_trip_duration_under_33_1min['Trip Duration'].std())) & 
    (df_oct_2019_trip_duration_under_33_1min['Trip Duration'] < (df_oct_2019_trip_duration_under_33_1min['Trip Duration'].mean() + df_oct_2019_trip_duration_under_33_1min['Trip Duration'].std()))
]['Trip Duration'].count()
trip_duration_in_mean_pm_std_percent_33_1min = trip_duration_in_mean_pm_std_33_1min / df_oct_2019_trip_duration_under_33_1min['Trip Duration'].count()
print('Anzahl der Beobachtungen, die im Bereich mean+-std liegen: {:.2f}% ({} Trips)'.format(trip_duration_in_mean_pm_std_percent_33_1min*100, trip_duration_in_mean_pm_std_33_1min))
Anzahl der Beobachtungen, die im Bereich mean+-std liegen: 70.47% (3348 Trips)

Gegenüber den anfänglichen Zahlen mit allen Ausreissern, sind nun folgende Änderungen durch Entfernen der Ausreisser ersichtlich:

  • Der Median liegt nun bei 9.6 Minuten (mit Ausreisser 10.1 Minuten).
  • Ein Trip dauert durchschnittlich nun 11.6 Minuten (mit Ausreisser 15.4 Minuten).
  • Der längste Trip dauert 33.1 Minuten und nicht mehr 4.4 Tage und der kürzeste immer noch 1.1 Minuten.
  • 5% der Trips dauern mehr als 27.3 Minuten und 5% der Trips weniger als 3 Minuten.
  • Die Standardabweichung beträgt nun 7.4min (nicht mehr 92.92min) und 70.47% (3348 Trips) der Beobachtungen befinden sich im Bereich $11.56\pm7.35 min$. Wir haben uns nun auch etwas an eine Normalverteilung angenähert, bei welcher 68% in diesem Bereich liegen.

Die Bikes werden eher nur sehr kurze Zeit genutzt. Dies hängt vermutlich mit dem Preismodell zusammen, bei welchem 30 minuten pro Trip für Customers gratis sind. Bei einem Subscriber sind 45 Minuten gratis. Die einzelnen Gruppen werden noch in einem Boxplot miteinander verglichen.

In [37]:
def  get_data_for_boxplot_duration(max_duration_hours = np.Inf):
    
    df = df_oct2019[df_oct2019['Trip Duration (h)'] <= max_duration_hours]
    
    data = [
        df[df['User Type'] == 'Customer']['Trip Duration (min)'],
        df[df['User Type'] == 'Subscriber']['Trip Duration (min)'],
        df[df['Gender'] == '0']['Trip Duration (min)'],
        df[df['Gender'] == '1']['Trip Duration (min)'],
        df[df['Gender'] == '2']['Trip Duration (min)'],
        df[(df['User Type'] == 'Customer') & (df['Gender'] == '0')]['Trip Duration (min)'],
        df[(df['User Type'] == 'Customer') & (df['Gender'] == '1')]['Trip Duration (min)'],
        df[(df['User Type'] == 'Customer') & (df['Gender'] == '2')]['Trip Duration (min)'],
        df[(df['User Type'] == 'Subscriber') & (df['Gender'] == '0')]['Trip Duration (min)'],
        df[(df['User Type'] == 'Subscriber') & (df['Gender'] == '1')]['Trip Duration (min)'],
        df[(df['User Type'] == 'Subscriber') & (df['Gender'] == '2')]['Trip Duration (min)'],
    ]
    labels = [
        'Customers',
        'Subscribers',
        'Gender Unknown',
        'Male',
        'Women',
        'Customers & Gender Unknown',
        'Customers & Male',
        'Customers & Woman',
        'Subscribers & Gender Unknown',
        'Subscribers & Male',
        'Subscribers & Woman',
    ]
    
    return {'data': data, 'labels': labels}
In [38]:
fig, ax = plt.subplots(figsize=(30, 10))

dict_data = get_data_for_boxplot_duration(1)
_ = ax.boxplot(dict_data['data'], vert=True, showmeans=True, labels=dict_data['labels'], showfliers=False)
ax.set_ylabel('min')
ax.set_title('Vergleich Trip Duration (nur Trips <= 1h)')

ax.set_axisbelow(True)
ax.yaxis.grid(linestyle='dashed')

#plt.tight_layout()
plt.show()

Folgendes fällt hier auf:

  • Trips von Customern dauern länger als solche von Subscribern.
  • Die Frauen nutzen die Bikes länger als die Männer. Benutzer die kein Geschlecht angeben, fahren länger als solche die ihr Geschlecht angeben.
  • Bei den Subscribern ist mit Berücksichtigung des Geschlechts kein grosser Unterschied zu sehen und die durchschnittliche Dauer der Fahrten liegt nahe beieinander.

Obwohl ein Subscriber 45 Minuten pro Trip gratis fahren könnte, verwendet dieser die Bikes weniger lange als ein Customer mit nur 30 Minuten inklusive. Es wird vermutet, dass Customer mehrheitlich Touristen sind, welche Sightseeing betreiben und Subscriber in New York leben und das Bike nutzen, um z.B. zur Arbeit zu fahren.

Age 2020

Ein paar Untersuchungen zu Age 2020.

In [39]:
df_oct2019[['Age 2020']].describe(percentiles=[0.05, .25, .5, .75, .95])
Out[39]:
Age 2020
count 5000.00000
mean 39.61800
std 12.01451
min 17.00000
5% 24.00000
25% 30.00000
50% 37.00000
75% 51.00000
95% 60.00000
max 134.00000

Erkenntnisse aus der Übersicht:

  • Der älteste Benutzer scheint hier 134 Jahre alt zu sein. Hier handelt es sich wohl um eine nicht korrekte Angabe des Alters.
  • Der Jüngste Benuzter ist 17 Jahre alt.
  • Im Durschnitt fahren 39 jährige Benutzer die Trips.
  • Die über 60 jährigen fahren 5% der Trips.
In [40]:
print('Anzahl unterschiedlicher Werte in "Age 2020": {}'.format(len(df_oct2019['Age 2020'].unique())))
Anzahl unterschiedlicher Werte in "Age 2020": 67

Zwar ist Age 2020 diskret, aber da es doch 67 unterschiedliche Werte hat, wird das Merkmal einfach mal als quasi-stetig behandelt und in einem Histogramm visualisiert.

In [41]:
_ = show_hist(df_oct2019['Age 2020'], 'Jahre', 'Anzahl', 'Age 2020 im Oktober 2019')
Info Histogram: [ Klassen: 20, Breite: 5.85000, Skew: 0.61146, Kurt: 0.47641 ]

In der Nähe der Klasse mit 30 Jahren und der Klasse mit 50 Jahren gibt es zwei Anhäufungen. Die meisten Nutzer sind also etwa 30 oder 50 Jahre alt. Rechts scheint es noch ein paar Ausreisser zu geben, diese wären dann über 80 Jahre alt. Um diese auch noch zu sehen, hier das Histogramm nochmals mit logarithmischer y-Achse.

In [42]:
_ = show_hist(df_oct2019['Age 2020'], 'Jahre', 'Anzahl', 'Age 2020 im Oktober 2019', log=True)
Info Histogram: [ Klassen: 20, Breite: 5.85000, Skew: 0.61146, Kurt: 0.47641 ]

Das Histogramm zeigt, dass die meisten unter 80 Jahre alt sind, darüber gibt es nur wenige Klassen. Die beiden Klassen ab 120 Jahre sind sehr unrealistisch und hier wurden vermutlich falsche Angaben gemacht.

In [43]:
_ = show_box(df_oct2019['Age 2020'], 'Jahre', 'Alter im Jahr 2020', labels=['Age 2020'])

Im Boxplot ist ersichtlich,

  • dass der Durchschnitt etwas unter 40 Jahre liegt (wie oben schon gezeigt liegt dieser bei genau 39.6 Jahren).
  • der Median bei 37 Jahren.
  • es gegen oben ein paar Ausreisser gibt, die über 80 Jahre alt sind.
In [44]:
def  get_data_for_boxplot_age2020(max_age = np.Inf):
    
    df = df_oct2019[df_oct2019['Age 2020'] <= max_age]
    
    data = [
        df[df['User Type'] == 'Customer']['Age 2020'],
        df[df['User Type'] == 'Subscriber']['Age 2020'],
        df[df['Gender'] == '0']['Age 2020'],
        df[df['Gender'] == '1']['Age 2020'],
        df[df['Gender'] == '2']['Age 2020'],
        df[(df['User Type'] == 'Customer') & (df['Gender'] == '0')]['Age 2020'],
        df[(df['User Type'] == 'Customer') & (df['Gender'] == '1')]['Age 2020'],
        df[(df['User Type'] == 'Customer') & (df['Gender'] == '2')]['Age 2020'],
        df[(df['User Type'] == 'Subscriber') & (df['Gender'] == '0')]['Age 2020'],
        df[(df['User Type'] == 'Subscriber') & (df['Gender'] == '1')]['Age 2020'],
        df[(df['User Type'] == 'Subscriber') & (df['Gender'] == '2')]['Age 2020'],
    ]
    labels = [
        'Customers',
        'Subscribers',
        'Gender Unknown',
        'Male',
        'Women',
        'Customers & Gender Unknown',
        'Customers & Male',
        'Customers & Woman',
        'Subscribers & Gender Unknown',
        'Subscribers & Male',
        'Subscribers & Woman',
    ]
    
    return {'data': data, 'labels': labels}
In [45]:
fig, ax = plt.subplots(figsize=(30, 10))

dict_data = get_data_for_boxplot_age2020(80)
_ = ax.boxplot(dict_data['data'], vert=True, showmeans=True, labels=dict_data['labels'], showfliers=False)
ax.set_ylabel('Jahre')
ax.set_title('Vergleich Age 2020 (nur Alter <= 80 Jahre)')
plt.grid(b=True)

#plt.tight_layout()
plt.show()

Im Vergleich ist ersichtlich:

  • Die Customer sind im Durchschnitt ca. 2 Jahre jünger als die Subscriber. Der Median ist aber bei den Subscribern viel tiefer als bei den Customern.
  • Wenn das Geschlecht nicht angegeben wird, dann ist man im Durchschnitt 50 Jahre alt. Der Median liegt hier ein Jahr höher bei 51 Jahren. (Vielleicht ist hier bei der Anmeldung ein Wert voreingestellt welcher dann einfach übernommen wird?!)
  • Bei den Customern sind Männer und Frauen im Durchschnitt ca. gleich alt. Bei den Subscribern sind die Frauen im Durchschnitt etwas jünger.

Anzahl Trips nach Alter

Untersuchung der Anzahl Trips nach Alter.

In [46]:
trip_count_customers_groupby_age = df_oct2019[df_oct2019['User Type'] == 'Customer'].groupby(by=['Age 2020', 'Gender'])['Trip Duration'].agg('count').reset_index().rename({'Trip Duration':'Trip count'}, axis=1)
trip_count_customers_groupby_age = trip_count_customers_groupby_age.pivot(index='Age 2020', columns='Gender', values='Trip count')
trip_count_customers_groupby_age.columns = ['unknown', 'male', 'female']
#trip_count_customers_groupby_age.head()
In [47]:
trip_count_subscribers_groupby_age = df_oct2019[df_oct2019['User Type'] == 'Subscriber'].groupby(by=['Age 2020', 'Gender'])['Trip Duration'].agg('count').reset_index().rename({'Trip Duration':'Trip count'}, axis=1)
trip_count_subscribers_groupby_age = trip_count_subscribers_groupby_age.pivot(index='Age 2020', columns='Gender', values='Trip count')
trip_count_subscribers_groupby_age.columns = ['unknown', 'male', 'female']
#trip_count_subscribers_groupby_age.head()
In [48]:
fig, ax = plt.subplots(nrows=2, ncols=1, figsize=(20, 8), sharex=True, sharey=False)

trip_count_customers_groupby_age.plot(ax=ax[0], kind='bar', stacked=True, width=0.9)
ax[0].set_axisbelow(False)
ax[0].yaxis.grid(linestyle='dashed')
ax[0].xaxis.grid(linestyle='dashed')
ax[0].set_ylabel('Trip Count')
ax[0].set_title('Customers - Anzahl Trips nach Alter')

trip_count_subscribers_groupby_age.plot(ax=ax[1], kind='bar', stacked=True, width=0.9)
ax[1].set_axisbelow(False)
ax[1].yaxis.grid(linestyle='dashed')
ax[1].xaxis.grid(linestyle='dashed')
ax[1].set_ylabel('Trip Count')
ax[1].set_title('Subscribers - Anzahl Trips nach Alter')

plt.tight_layout()
plt.show()

In den beiden obenstehenden Grafiken ist folgendes ersichtlich:

  • Die meisten Customer, die ihr Geschlecht bei einem Trip nicht angeben, sind 50 Jahre alt. Bei den Subscribern geben hauptsächlich die 51 jährigen ihr Geschlecht nicht an.
  • Am meisten Trips wurden durch 30 jährige gefahren. Wobei hier der Anteil der Fahrten von Männern höher ist, als der der Frauen.

Station

Ein paar Untersuchungen zu den Stationen.

In [49]:
df_oct2019[[col for col in df_oct2019.columns if col.endswith('Station Latitude') or col.endswith('Station Longitude') or col.endswith('Station Name') or col == 'Linear Distance']].describe(include='all')
Out[49]:
Start Station Name Start Station Latitude Start Station Longitude End Station Name End Station Latitude End Station Longitude Linear Distance
count 5000 5000.00000 5000.00000 5000 5000.00000 5000.00000 5000.00000
unique 715 nan nan 714 nan nan nan
top 8 Ave & W 31 St nan nan Pershing Square North nan nan nan
freq 34 nan nan 50 nan nan nan
mean NaN 40.73813 -73.98222 NaN 40.73742 -73.98255 1.75303
std NaN 0.02969 0.01951 NaN 0.02940 0.01981 1.36817
min NaN 40.65709 -74.02535 NaN 40.65540 -74.02535 0.00000
25% NaN 40.71910 -73.99530 NaN 40.71850 -73.99596 0.83659
50% NaN 40.73902 -73.98518 NaN 40.73827 -73.98658 1.34903
75% NaN 40.75763 -73.97189 NaN 40.75643 -73.97209 2.24275
max NaN 40.81830 -73.90774 NaN 40.81830 -73.90904 11.32096

Erkenntnisse aus der Übersicht:

  • Start und End Station ID haben das Skalenniveau 'nominal', daher machen die Werte für diese beiden Spalten keinen Sinn und wurden daher weggelassen.
  • Start und End Station Name sind zwar auch 'nominal', aber die Übersicht gibt hier doch ein paar Werte aus die sinnvoll sind.
  • Es gibt 715 verschiedene Stationen von welchen aus Trips gestartet wurden und 714 verschiedene Stationen an welchen Trips endeten.
  • Top Start und End Station ist 'Persing Square North'. Es wurden 34 Trips ab dieser Station gestartet und 50 Trips endeten an der Station.
  • Örtlich liegen die Stationen nahe beeinander, denn die Standardabweichung ist sehr klein. Auch der minimale und maximale Wert bei Latitude und Longitude liegen nahe beieinander. Bei Latitude nicht mehr als 18km und bei Longitude nicht mehr als 13km.
  • Die Stationen liegen nie weiter als 11km Luftlinie auseinander. Dies sieht man am Maximalwert in der Spalte 'Linear Distance'.
  • Bei 'Linear Distance' ist der minimale Wert 0km, dies weil Trips an der gleichen Station enden können an welcher sie gestartet wurden.
  • Durchschnittlich enden die Trips an einer 1.8km entfernten Station.

Distanz der Trips nach Alter

Untersuchung der Distanz der Trips nach Alter.

In [50]:
trip_distance_customers_groupby_age = df_oct2019[df_oct2019['User Type'] == 'Customer'].groupby(by=['Age 2020', 'Gender'])['Linear Distance'].agg('sum').reset_index().rename({'Linear Distance':'Linear Distance sum'}, axis=1)
trip_distance_customers_groupby_age = trip_distance_customers_groupby_age.pivot(index='Age 2020', columns='Gender', values='Linear Distance sum')
trip_distance_customers_groupby_age.columns = ['unknown', 'male', 'female']
#trip_distance_customers_groupby_age.head()
In [51]:
trip_distance_subscribers_groupby_age = df_oct2019[df_oct2019['User Type'] == 'Subscriber'].groupby(by=['Age 2020', 'Gender'])['Linear Distance'].agg('sum').reset_index().rename({'Linear Distance':'Linear Distance sum'}, axis=1)
trip_distance_subscribers_groupby_age = trip_distance_subscribers_groupby_age.pivot(index='Age 2020', columns='Gender', values='Linear Distance sum')
trip_distance_subscribers_groupby_age.columns = ['unknown', 'male', 'female']
#trip_distance_subscribers_groupby_age.head()
In [52]:
fig, ax = plt.subplots(nrows=2, ncols=1, figsize=(20, 8), sharex=True, sharey=False)

trip_distance_customers_groupby_age.plot(ax=ax[0], kind='bar', stacked=True, width=0.9)
ax[0].set_axisbelow(False)
ax[0].yaxis.grid(linestyle='dashed')
ax[0].xaxis.grid(linestyle='dashed')
ax[0].set_ylabel('Linear Distance (km)')
ax[0].set_title('Customers - Anzahl Kilometer nach Alter')

trip_distance_subscribers_groupby_age.plot(ax=ax[1], kind='bar', stacked=True, width=0.9)
ax[1].set_axisbelow(False)
ax[1].yaxis.grid(linestyle='dashed')
ax[1].xaxis.grid(linestyle='dashed')
ax[1].set_ylabel('Linear Distance (km)')
ax[1].set_title('Subscribers - Anzahl Kilometer nach Alter')

plt.tight_layout()
plt.show()

In den beiden obenstehenden Grafiken ist folgendes ersichtlich:

  • Die 50 jährigen Customer, die am meisten Trips gefahren sind, legen auch die grösste Distanz zurück.
  • Die 28 jährigen legen bei den Subscribern am meiten Kilometer zurück.

Top Stationen

Hier soll untersucht werden, welche Stationen am meisten benutzt werden.

In [53]:
starts_count = df_oct2019.groupby(by=['Start Station Name'])['Trip Duration'].agg('count').reset_index().rename({'Trip Duration' :'Starts count', 'Start Station Name': 'Station Name'}, axis=1).set_index('Station Name')
#starts_count.head()
In [54]:
ends_count = df_oct2019.groupby(by=['End Station Name'])['Trip Duration'].agg('count').reset_index().rename({'Trip Duration' :'Ends count', 'End Station Name': 'Station Name'}, axis=1).set_index('Station Name')
#ends_count.head()
In [55]:
start_stations = df_oct2019[[col for col in df_oct2019.columns if col.startswith('Start Station')]].rename({'Start Station ID': 'Station ID', 'Start Station Name': 'Station Name', 'Start Station Latitude': 'Latitude', 'Start Station Longitude': 'Longitude'}, axis=1).set_index('Station Name')
start_stations = start_stations[start_stations.duplicated() == False]
#start_stations
In [56]:
end_stations = df_oct2019[[col for col in df_oct2019.columns if col.startswith('End Station')]].rename({'End Station ID': 'Station ID', 'End Station Name': 'Station Name', 'End Station Latitude': 'Latitude', 'End Station Longitude': 'Longitude'}, axis=1).set_index('Station Name')
end_stations = end_stations[end_stations.duplicated() == False]
#end_stations
In [57]:
stations = pd.concat([start_stations, end_stations])
stations = stations[stations.duplicated() == False]
#stations
In [58]:
trip_counts_by_station = pd.concat([starts_count, ends_count, stations], axis=1)
trip_counts_by_station.insert(2, 'Trip count', trip_counts_by_station[['Starts count', 'Ends count']].sum(axis=1))
trip_counts_by_station = trip_counts_by_station.sort_values(by='Trip count', ascending=False)
trip_counts_by_station.index.name = 'Station Name'
#trip_counts_by_station.head()
In [59]:
fig, ax = plt.subplots(figsize=(20, 8))

trip_counts_by_station[0:50][['Starts count', 'Ends count']].plot(ax=ax, kind='bar', stacked=True, width=0.9)
ax.set_axisbelow(False)
ax.yaxis.grid(linestyle='dashed')
ax.xaxis.grid(linestyle='dashed')
ax.set_ylabel('Trip count')
ax.set_title('Top 50 Stationen')

plt.tight_layout()
plt.show()

Die 50 Stationen mit den meisten Trips wurden in einem Barplot dargestellt.

  • Die Station 'Pershing Square North' ist mit über 80 Trips an erster Stelle.
  • Die Station 'W49 St & 8 Ave' bildet mit ca. 35 Trips das Schlusslicht der Top 50.

Die Top 50 Stationen werden noch auf einer Map dargestellt. Dabei stellt die Grösse und die Farbe des Kreises die Anzahl Trips dar.

Tip 50 Stationen auf einer Karte

In [60]:
import tilemapbase
tilemapbase.init(create=True)
tiles_osm = tilemapbase.tiles.Carto_Light

#center_nyc = (-73.967419961490, 40.73100351879932)
center_nyc = (-73.984803, 40.745687)

degree_range_heigth = 0.07
degree_range_width = 0.06
extent = tilemapbase.Extent.from_lonlat(
    center_nyc[0] - degree_range_heigth, center_nyc[0] + degree_range_heigth, 
    center_nyc[1] - degree_range_width, center_nyc[1] + degree_range_width)
extent = extent.to_aspect(1.0)
In [61]:
projection = trip_counts_by_station[0:50].apply(lambda row: tilemapbase.project(latitude=row['Latitude'], longitude=row['Longitude']), axis=1)

x_coords = []
y_coords =[]
names = []
counts = []

for idx, val in enumerate(projection.values):
    x_coords.append(val[0])
    y_coords.append(val[1])
    names.append(trip_counts_by_station[0:50].reset_index()['Station Name'][idx])
    counts.append(trip_counts_by_station[0:50]['Trip count'][idx])
In [62]:
fig, ax = plt.subplots(figsize=(10, 10), dpi=150)
ax.xaxis.set_visible(False)
ax.yaxis.set_visible(False)

plotter = tilemapbase.Plotter(extent, tiles_osm, width=600)
plotter.plot(ax, tiles_osm)

sc = ax.scatter(x_coords, y_coords, c=counts, s=counts, cmap=plt.cm.get_cmap('viridis'), alpha=0.9, linewidths=3)

cbar = plt.colorbar(sc, fraction=0.046, pad=0.02, label='Anzahl Trips', orientation='vertical')

ax.set_title('Top 50 Stationen')

#plt.tight_layout()
plt.show()

Auf der Karte ist die Station 'Pershing Square North' mit einem gelben Punkt markiert, diese Station hat auch die meisten Trips. Diese Station liegt am Grand Central Terminal, was vermutlich auch der Grund für die stärkere Auslastung ist.

temp

Untersuchungen zur Temperatur.

In [63]:
df_oct2019['temp'].describe()
Out[63]:
count   5000.00000
mean      16.61370
std        3.98866
min        6.09000
25%       14.06000
50%       16.02000
75%       18.41000
max       33.07000
Name: temp, dtype: float64
  • Die Temperatur im Oktober 2019 war im Durchschnitt 16.6°C.
  • Tiefste Temperatur war 6°C.
  • Höchste Temperatur war 33.1°C.
In [64]:
_ = show_hist(df_oct2019['temp'], '°C', 'Anzahl', 'Temperatur im Oktober 2019')
Info Histogram: [ Klassen: 20, Breite: 1.34900, Skew: 1.14378, Kurt: 2.33898 ]

Wie oben bei der Übersicht mit den Zahlen ersichtlich, häufen sich im Histogramm die Werte zwischen 14 und 18°C.

In [65]:
_ = show_box(df_oct2019['temp'], '°C', 'Temperatur im Oktober 2019', labels=['temp'])

Im Boxplot ist auch die Häufung zwischen 14 und 18°C sofort ersichtlich. Auch sieht man die Ausreisser, welche bedeuten, dass manche Trips bei einer Temperatur von z.B. über 30°C gefahren wurden.

Damit eine Aussage darüber gemacht werden kann, wie die Temperatur an welchen Tagen war, wird ein Plot über den Monat Oktober 2019 erstellt.

In [66]:
fig = plt.figure(figsize=(20, 5))

ax = fig.add_subplot()
_ = df_oct2019.sort_values('Start Time')[['Start Time', 'temp']].set_index('Start Time').plot(ax=ax, y='temp')

ax.set_xlabel('Start Time')
ax.set_ylabel('°C')
ax.set_title('Temperatur im Oktober 2019')

ax.yaxis.grid(linestyle='dashed')
ax.xaxis.grid(linestyle='dashed')

ax.xaxis.set_major_locator(mdates.DayLocator(interval=1))
ax.xaxis.set_major_formatter(mdates.DateFormatter("%d"))

ax.set_ylim(0, 35)
ax.set_xlim([datetime(2019, 10, 1, 0, 0, 0, tzinfo=tz.gettz('US/Eastern')), datetime(2019, 11, 1, 0, 0, 0, tzinfo=tz.gettz('US/Eastern'))])

ax.tick_params(axis='x', labelrotation=90)
for tick in ax.xaxis.get_major_ticks():
    tick.label.set_horizontalalignment('left')
    
#plt.tight_layout()
plt.show()
  • Der Tag mit der höchsten Temperatur war anfangs Oktober, am 2.10.2019.
  • Am Tiefsten war die Temperatur am 19.10.2019.

Diese beiden Tage werden in den nächsten beiden Kapiteln noch in einem eigenen Tagesverlauf dargestellt.

Temperaturverlauf am Tag mit höchstem Temperaturwert

In [67]:
df_oct2019[['Start Time', 'temp']].sort_values('temp', ascending=False).head(1)
Out[67]:
Start Time temp
129275 2019-10-02 14:38:04-04:00 33.07000

Der Tag mit der höchsten Temperatur ist der 2.10.2019.

In [68]:
fig = plt.figure(figsize=(20, 5))

ax = fig.add_subplot()
_ = df_oct2019[['Start Time', 'temp']].set_index('Start Time')['2019-10-02'].plot(ax=ax, y='temp')

ax.set_xlabel('Start Time')
ax.set_ylabel('°C')
ax.set_title('Temperatur am 2.10.2019')

ax.yaxis.grid(linestyle='dashed')
ax.xaxis.grid(linestyle='dashed')
             
ax.xaxis.set_major_locator(mdates.HourLocator(interval=1))
ax.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M"))

ax.set_ylim(0, 35)
ax.set_xlim([datetime(2019, 10, 2, 0, 0, 0, tzinfo=tz.gettz('US/Eastern')), datetime(2019, 10, 3, 0, 0, 0, tzinfo=tz.gettz('US/Eastern'))])


ax.tick_params(axis='x', labelrotation=90)
for tick in ax.xaxis.get_major_ticks():
    tick.label.set_horizontalalignment('left')
    
#plt.tight_layout()
plt.show()

Die Höchsttemperatur von 33.1°C wurde gegen 15 Uhr erreicht. Die anderen Trips wurden immer mit Temperaturen über 22 Grad gefahren, erst Abends ab ca. 22:30 fiel die Temperatur unter 22 °C.

Temperaturverlauf am Tag mit tiefstem Temperaturwert

In [69]:
df_oct2019[['Start Time', 'temp']].sort_values('temp', ascending=True).head(1)
Out[69]:
Start Time temp
1278545 2019-10-19 07:06:40-04:00 6.09000

Der Tag mit der tiefsten Temperatur ist der 19.10.2019.

In [70]:
fig = plt.figure(figsize=(20, 5))

ax = fig.add_subplot()
_ = df_oct2019[['Start Time', 'temp']].set_index('Start Time')['2019-10-19'].plot(ax=ax, y='temp')

ax.set_xlabel('Start Time')
ax.set_ylabel('°C')
ax.set_title('Temperatur am 19.10.2019')

ax.yaxis.grid(linestyle='dashed')
ax.xaxis.grid(linestyle='dashed')
             
ax.xaxis.set_major_locator(mdates.HourLocator(interval=1))
ax.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M"))

ax.set_ylim(0, 20)
ax.set_xlim([datetime(2019, 10, 19, 0, 0, 0, tzinfo=tz.gettz('US/Eastern')), datetime(2019, 10, 20, 0, 0, 0, tzinfo=tz.gettz('US/Eastern'))])

ax.tick_params(axis='x', labelrotation=90)
for tick in ax.xaxis.get_major_ticks():
    tick.label.set_horizontalalignment('left')

    
#plt.tight_layout()
plt.show()

Die Tiefsttemperatur von 6.1°C wurde um 7 Uhr erreicht. Danach wurde es wieder etwas wärmer.

rain_1h

Untersuchungen zum Niederschlag.

In [71]:
_ = show_hist(df_oct2019['rain_1h'], 'mm', 'Anzahl', 'Niederschlag im Oktober 2019')
Info Histogram: [ Klassen: 20, Breite: 0.39050, Skew: 9.92870, Kurt: 120.06672 ]

Im Histogramm ist nur ersichtlich das die Werte sich in der Nähe von 0 häufen, daher noch ein Histogramm mit logarithmischer y-Achse.

In [72]:
_ = show_hist(df_oct2019['rain_1h'], 'mm', 'Anzahl', 'Niederschlag im Oktober 2019', log=True)
Info Histogram: [ Klassen: 20, Breite: 0.39050, Skew: 9.92870, Kurt: 120.06672 ]

Hier lässt sich herauslesen, dass es meistens bis zu 2mm Niederschlag gab und manchmal auch mehr. Aber an welchen Tagen es im Oktober 2019 geregnet hat, lässt sich nicht erkennen.

In [73]:
_ = show_box(df_oct2019['rain_1h'], 'mm', 'Niederschlag im Oktober 2019', labels=['rain_1h'])

Der Boxplot zeigt das gleiche Bild wie das Histogramm, d.h. der Median liegt bei 0mm, der Durchschnitt etwas über 0mm und alle anderen Werte werden als Ausreisser markiert.

Die Übersicht in Zahlen zum Merkmal rain_1h:

In [74]:
df_oct2019['rain_1h'].describe()
Out[74]:
count   5000.00000
mean       0.08230
std        0.42080
min        0.00000
25%        0.00000
50%        0.00000
75%        0.00000
max        7.81000
Name: rain_1h, dtype: float64
  • Im Monat Oktober 2019 hat es durchschnittlich 0.0823mm Niederschlag gegeben.
  • Sehr viele Werte sind hier 0, dadurch sind die Quartile auch 0.
  • Der Maximalwert ist 7.81mm.

Damit nun auch ersichtlich ist, an welchen Tagen es wie viel Niederschlag gab, wird ein Plot über den ganzen Monat erstellt.

In [75]:
fig = plt.figure(figsize=(20, 5))

ax = fig.add_subplot()
_ = df_oct2019.sort_values('Start Time')[['Start Time', 'rain_1h']].plot(ax=ax, x='Start Time', y='rain_1h')

ax.set_xlabel('Start Time')
ax.set_ylabel('mm')
ax.set_title('Niederschlag im Oktober 2019')

ax.yaxis.grid(linestyle='dashed')
ax.xaxis.grid(linestyle='dashed')

ax.xaxis.set_major_locator(mdates.DayLocator(interval=1))
ax.xaxis.set_major_formatter(mdates.DateFormatter("%d"))

ax.set_xlim([datetime(2019, 10, 1, 0, 0, 0, tzinfo=tz.gettz('US/Eastern')), datetime(2019, 11, 1, 0, 0, 0, tzinfo=tz.gettz('US/Eastern'))])

ax.tick_params(axis='x', labelrotation=90)
for tick in ax.xaxis.get_major_ticks():
    tick.label.set_horizontalalignment('left')

#plt.tight_layout()
plt.show()

Am meisten Niederschlag von 7.81mm gab es am 27. Oktober ca. gegen Mittag. Und am 16. Oktober hat es auch etwas über 7mm Niederschlag gegen Abend gegeben.

Analyse summary-daily-subscribers_only-citibike-tripweather.parquet

Hier werden ein paar Untersuchungen zur täglichen Zusammenfassung gemacht. Zu beachten ist, dass diese Zusammenfassung nur Informationen über Subscriber enthält und über alle Jahre gemacht wurde!

In diesem Kapitel wird nicht mehr alles so detailliert beschrieben wie für den Oktober 2019!

Merkmale

In [76]:
df_sumsubs = pd.read_parquet(os.path.join(path, 'summary-daily-subscribers_only-citibike-tripweather.parquet'))
In [77]:
df_sumsubs.tail().T
Out[77]:
Date 2020-04-26 2020-04-27 2020-04-28 2020-04-29 2020-04-30
Trip count 7658.00000 14942.00000 28808.00000 17793.00000 12297.00000
Trip Duration mean 909.11413 909.51151 1162.71529 944.66768 899.94633
Trip Duration std 872.16502 867.31142 1077.94083 873.58187 879.19177
Trip Duration min 61.00000 61.00000 61.00000 61.00000 61.00000
Trip Duration median 639.50000 659.00000 894.00000 690.00000 640.00000
Trip Duration max 17985.00000 21000.00000 20394.00000 16614.00000 19953.00000
Linear Distance mean 1.77203 1.85279 2.09608 1.92779 1.77484
Linear Distance std 1.69919 1.69954 1.84255 1.78455 1.67552
Linear Distance min 0.00000 0.00000 0.00000 0.00000 0.00000
Linear Distance median 1.24292 1.34243 1.58105 1.39203 1.27195
Linear Distance max 15.28464 13.50936 14.51794 14.12451 16.66022
Age 2020 count 7658.00000 14942.00000 28808.00000 17793.00000 12297.00000
Age 2020 mean 39.81431 40.77379 39.99729 40.47935 40.64471
Age 2020 std 11.95044 12.24587 12.11880 12.16765 12.09568
Age 2020 min 17.00000 16.00000 16.00000 16.00000 17.00000
Age 2020 median 37.00000 38.00000 37.00000 38.00000 38.00000
Age 2020 max 80.00000 80.00000 81.00000 81.00000 81.00000
temp mean 9.09741 6.84308 10.30875 10.79875 11.83769
temp std 0.82874 1.61124 4.86921 1.75832 1.89143
temp median 9.16000 6.69000 7.74500 10.74500 10.82500
temp min 7.91000 4.51000 4.98000 8.38000 10.02000
temp max 11.37000 9.64000 18.25000 14.69000 15.11000
humidity mean 75.74074 77.42308 59.37500 74.04167 84.80769
humidity std 14.62241 10.83761 17.34512 10.46518 6.98008
humidity median 76.00000 81.00000 66.00000 76.00000 87.00000
humidity min 40.00000 57.00000 25.00000 50.00000 71.00000
humidity max 93.00000 93.00000 75.00000 87.00000 93.00000
wind_speed mean 5.79926 3.18462 3.21000 4.74708 6.13077
wind_speed std 2.47887 1.32082 1.68901 2.19028 1.59819
wind_speed median 5.68000 3.10000 2.60000 4.85000 6.20000
wind_speed min 2.10000 1.50000 1.50000 1.50000 3.10000
wind_speed max 9.80000 7.20000 7.15000 8.70000 8.70000
rain_1h sum 5.27000 1.95000 0.00000 0.29000 10.32000
rain_1h min 0.00000 0.00000 0.00000 0.00000 0.00000
rain_1h max 1.01000 0.35000 0.00000 0.29000 1.27000
snow_1h sum 0.00000 0.00000 0.00000 0.00000 0.00000
snow_1h min 0.00000 0.00000 0.00000 0.00000 0.00000
snow_1h max 0.00000 0.00000 0.00000 0.00000 0.00000
Statistische Einheit Merkmal Skalenniveau Kontinuität Beschreibung
Day Date Intervall diskret Datum
Trip Trip count Verhältnis diskret Gesamte Anzahl Trips an diesem Tag
Trip Trip Duration mean Verhältnis stetig Durchschnittliche Dauer
Trip Trip Duration std Verhältnis stetig Standardabweichung der Dauer
Trip Trip Duration min Verhältnis stetig Dauer des schnellsten Trips
Trip Trip Duration median Verhältnis stetig Median der Dauer
Trip Trip Duration max Verhältnis stetig Dauer des längsten Trips
Trip Linear Distance mean Verhältnis stetig Durchschnittliche Distanz (Luftlinie zwischen Stationen)
Trip Linear Distance std Verhältnis stetig Standardabweichung der Distanz
Trip Linear Distance min Verhältnis stetig Distanz des kürzesten Trips
Trip Linear Distance median Verhältnis stetig Median der Distanz
Trip Linear Distance max Verhältnis stetig Distanz des weitesten Trips
User Age 2020 count Verhältnis diskret Gesamte Anzahl angegebener Alter
User Age 2020 mean Verhältnis stetig Durchschnittliches Alter
User Age 2020 std Verhältnis diskret Standardabweichung des Alters
User Age 2020 min Verhältnis diskret Jüngstes Alter
User Age 2020 median Verhältnis stetig Median des Alters
User Age 2020 max Verhältnis diskret Ältestes Alter
Temperature temp mean Intervall stetig Durchschnittliche Temperatur (°C)
Temperature temp std Intervall stetig Standardabweichung der Temperatur (°C)
Temperature temp median Intervall stetig Median der Temperatur (°C)
Temperature temp min Intervall stetig Tiefste Temperatur (°C)
Temperature temp max Intervall stetig Höchste Temperatur (°C)
Wind wind_speed mean Verhältnis stetig Durchschnittliche Windgeschwindigkeit (m/s)
Wind wind_speed std Verhältnis stetig Standardabweichung der Windgeschwindigkeit (m/s)
Wind wind_speed median Verhältnis stetig Median der Windgeschwindigkeit (m/s)
Wind wind_speed min Verhältnis stetig Tiefste Windgeschwindigkeit (m/s)
Wind wind_speed max Verhältnis stetig Höchste Windgeschwindigkeit (m/s)
Rainfall rain_1h sum Verhältnis stetig Gesamte Menge an Niederschlag (mm)
Rainfall rain_1h min Verhältnis stetig Tiefste Menge an Niederschlag in 1h am Tag (mm)
Rainfall rain_1h max Verhältnis stetig Höchste Menge an Niederschlag in 1h am Tag (mm)
Snowfall snow_1h sum Verhältnis stetig Gesamte Menge an Schneefall (mm)
Snowfall snow_1h min Verhältnis stetig Tiefste Menge an Schneefall in 1h am Tag (mm)
Snowfall snow_1h max Verhältnis stetig Höchste Menge an Schneefall in 1h am Tag (mm)

Subscriber-Trips über alle Jahre

Eine Zusammenfassung der Trips von Subscribern über alle Jahre in Zahlen.

In [78]:
df_sumsubs['Trip count'].describe()
Out[78]:
count    2488.00000
mean    33624.25442
std     17238.81747
min       854.00000
25%     20760.00000
50%     31608.00000
75%     45691.25000
max     82687.00000
Name: Trip count, dtype: float64
In [79]:
subsubs_trip_count_mean_pm_std = df_sumsubs['Trip count'][
    (df_sumsubs['Trip count'] > (df_sumsubs['Trip count'].mean() - df_sumsubs['Trip count'].std())) & 
    (df_sumsubs['Trip count'] < (df_sumsubs['Trip count'].mean() + df_sumsubs['Trip count'].std()))
].count()
subsubs_trip_count_mean_pm_std_percent = subsubs_trip_count_mean_pm_std / df_sumsubs['Trip count'].count()
print('Anzahl der Werte, die im Bereich mean+-std liegen: {:.2f}% ({} Tage)'.format(subsubs_trip_count_mean_pm_std_percent*100, subsubs_trip_count_mean_pm_std))
Anzahl der Werte, die im Bereich mean+-std liegen: 65.19% (1622 Tage)

Anhand der Zahlen erkennt man, dass

  • durchschnittlich pro Tag 33624.25 Trips gefahren werden.
  • die Standardabweichung mit 17238.82 Trips zeigt, dass eine breite Streuung vorhanden ist.
  • an einem Tag nur schwache 854 Trips gefahren wurden, dies ist der tiefste Wert.
  • an einem Tag 82687 Trips gefahren wurden, dies ist der höchste Wert.

Es folgt das Histogramm dazu.

In [80]:
n, bins, patches = show_hist(df_sumsubs['Trip count'], 'Anzahl Trips', 'Anzahl', 'Trips von Subscribern über alle Jahre')
Info Histogram: [ Klassen: 20, Breite: 4091.65000, Skew: 0.39662, Kurt: -0.55595 ]
In [81]:
print('Grenzen: {}, Anzahl: {}'.format(bins, n))
Grenzen: [  854.    4945.65  9037.3  13128.95 17220.6  21312.25 25403.9  29495.55
 33587.2  37678.85 41770.5  45862.15 49953.8  54045.45 58137.1  62228.75
 66320.4  70412.05 74503.7  78595.35 82687.  ], Anzahl: [ 53. 105. 134. 169. 196. 238. 206. 282. 202. 144. 144. 135. 105.  98.
  94.  84.  55.  25.  12.   7.]

Das Histogramm zeigt eine Häufung in den Klassen die zwischen 20000 und 40000 Trips liegen. Die Grenzen der höchsten Klasse liegen zwischen 29495.55 und 33587.2 Trips.

Und der Boxplot zu den gleichen Daten.

In [82]:
boxplot = show_box(df_sumsubs['Trip count'], 'Anzahl Trips', 'Trips von Subscribern über alle Jahre', labels=['Trip count'])

Alle Daten liegen innerhalb der unauffälligen Streuung, d.h. es gibt keine Ausreisser. Ansonsten zeigt der Boxplot die Erkenntnisse die oben schon mit den Zahlen gewonnen wurden.

Monatliche Anzahl der Subscriber-Trips

Es wird eine monatliche Aggregation der Anzahl der Trips erstellt und in einem Plot dargestellt.

In [83]:
fig = plt.figure(figsize=(20, 5))

ax = fig.add_subplot()
_ = df_sumsubs['Trip count'].groupby(pd.Grouper(freq='M')).sum().plot(ax=ax, y='Trip count')

ax.set_xlabel('Jahr')
ax.set_ylabel('Anzahl')
ax.set_title('Anzahl Trips (Summe pro Monat)')

ax.yaxis.grid(linestyle='dashed')
ax.xaxis.grid(linestyle='dashed')

ax.ticklabel_format(axis='y', style='plain')
ax.get_yaxis().set_major_formatter(
    matplotlib.ticker.FuncFormatter(lambda x, p: format(int(x), ',')))

plt.show()

Hier ist erkennbar, dass

  • im 3. Quartal des Jahres 2019 am meisten Trips, mit 2'000'000 an der Anzahl, gefahren wurden.
  • ein Aufwärtstrend ersichtlich ist.
  • Um den Jahreswechsel herum, immer am wenigsten Trips gefahren werden.

Wind Speed

Ein paar Untersuchungen zur Windgeschwindigkeit.

In folgender Tabelle ist eine Übersicht über die Windgeschwindigkeit über alle Jahre aufgelistet.

In [84]:
df_sumsubs_wind_speed = df_sumsubs[[col for col in df_sumsubs.columns if col.startswith('wind_speed') & (col.endswith('mean') | col.endswith('median') | col.endswith('min') | col.endswith('max'))]]
In [85]:
df_sumsubs_wind_speed.describe()
Out[85]:
wind_speed mean wind_speed median wind_speed min wind_speed max
count 2488.00000 2488.00000 2488.00000 2488.00000
mean 3.19217 3.07145 1.47889 5.54780
std 1.33719 1.36614 0.92916 2.12569
min 1.21583 1.06000 0.01000 1.75000
25% 2.34438 2.10000 1.02000 4.10000
50% 2.85833 2.60000 1.50000 5.10000
75% 3.64031 3.60000 1.50000 6.70000
max 14.42750 14.13000 11.13000 18.91000

Zur einfacheren Interpretation wird die Windgeschwindigkeit noch in km/h umgerechnet.

In [86]:
df_sumsubs_wind_speed_kmh = df_sumsubs_wind_speed / 1000 *60 * 60
In [87]:
df_sumsubs_wind_speed_kmh.describe() 
Out[87]:
wind_speed mean wind_speed median wind_speed min wind_speed max
count 2488.00000 2488.00000 2488.00000 2488.00000
mean 11.49183 11.05722 5.32399 19.97207
std 4.81389 4.91810 3.34499 7.65250
min 4.37700 3.81600 0.03600 6.30000
25% 8.43975 7.56000 3.67200 14.76000
50% 10.29000 9.36000 5.40000 18.36000
75% 13.10512 12.96000 5.40000 24.12000
max 51.93900 50.86800 40.06800 68.07600

Interpretation der Zahlen:

  • Maximale Windgeschwindigkeit die je während eines Trips gemessen wurde, ist 68km/h.
  • Durchschnittlich herrscht ein Wind von 11.49km/h, mit einer Standardabweichung von 4.81km/h.
  • Die höchste durchschnittliche Windgeschwindigkeit an irgendeinem Tag betrug 51.94km/h.

Windgeschwindigkeit über alle Jahre

In [88]:
fig = plt.figure(figsize=(20, 5))

data_wind_speed_kmh_mean = df_sumsubs_wind_speed_kmh['wind_speed mean'].groupby(pd.Grouper(freq='M')).mean()
data_wind_speed_kmh_median = df_sumsubs_wind_speed_kmh['wind_speed median'].groupby(pd.Grouper(freq='M')).median()
data_wind_speed_kmh_min = df_sumsubs_wind_speed_kmh['wind_speed min'].groupby(pd.Grouper(freq='M')).min()
data_wind_speed_kmh_max = df_sumsubs_wind_speed_kmh['wind_speed max'].groupby(pd.Grouper(freq='M')).max()

ax = fig.add_subplot()
_ = ax.plot(data_wind_speed_kmh_mean.index, data_wind_speed_kmh_mean, label='wind_speed mean of daily mean')
_ = ax.plot(data_wind_speed_kmh_median.index, data_wind_speed_kmh_median, label='wind_speed median of daily median')
_ = ax.plot(data_wind_speed_kmh_min.index, data_wind_speed_kmh_min, label='wind_speed min')
_ = ax.plot(data_wind_speed_kmh_max.index, data_wind_speed_kmh_max, label='wind_speed max')

ax.set_xlabel('Jahr')
ax.set_ylabel('km/h')
ax.set_title('Windgeschwindigkeit')

ax.yaxis.grid(linestyle='dashed')
ax.xaxis.grid(linestyle='dashed')

plt.legend()
plt.show()

In der Grafik ist ersichtlich, dass

  • die höchste Windgeschwindigkeit gegen Ende 2019 gemessen wurde.
  • die durchschnittliche Windgeschwindigkeit bis ca. Mitte 2019 ungefähr gleich war, doch von Mitte 2019 bis Anfang 2020 immer durchschnittlich die doppelte Windgeschwindigkeit gemessen wurde. Dieser Anstieg fällt sofort auf.

Windgeschwindigkeit im Jahresvergleich

Durchschnittliche Windgeschwindigkeit pro Tag

Die durchschnittliche Windgeschwindigkeit pro Tag, wird in einem Boxplot, gruppiert nach Jahren, verglichen.

In [89]:
ax = (
    df_sumsubs_wind_speed_kmh
        .set_index(df_sumsubs_wind_speed_kmh.index.year)['wind_speed mean']
        .reset_index()
        .pivot(columns='Date', values='wind_speed mean')
        .apply(lambda x: pd.Series(x.dropna().values))
        .boxplot(vert=False, figsize=(20, 10), widths=0.7, showmeans=True)
)

ax.set_xlabel('km/h')
ax.set_ylabel('Jahr')
ax.set_title('Durchschnittliche Windgeschwindigkeit pro Tag')

plt.show()

Hier ist ersichtlich, dass

  • Die durchschnittliche Windgeschwindigkeit mit den Jahren zugenommen hat.
  • Im 2019 die durchschnittliche Windgeschwindigkeit am höchsten war, auch die Streuung der Werte ist hier am höchsten. Gleichzeitig gibt es im 2019 auch sehr hohe Ausreisser und wie in vorherigen Analysen festgestellt, ist der Maximalwert auch im 2019 erreicht worden.

Maximale Windgeschwindigkeit pro Tag

Die maximale Windgeschwindigkeit pro Tag, wird in einem Boxplot, gruppiert nach Jahren, verglichen.

In [90]:
ax = (
    df_sumsubs_wind_speed_kmh
        .set_index(df_sumsubs_wind_speed_kmh.index.year)['wind_speed max']
        .reset_index()
        .pivot(columns='Date', values='wind_speed max')
        .apply(lambda x: pd.Series(x.dropna().values))
        .boxplot(vert=False, figsize=(20, 10), widths=0.7, showmeans=True)
)

ax.set_xlabel('km/h')
ax.set_ylabel('Jahr')
ax.set_title('Maximale Windgeschwindigkeit pro Tag')

plt.show()

Schaut man sich die maximale Windgeschwindigkeit pro Tag an, dann ist ersichtlich, dass

  • der Wert durchschnittlich im 2020 am höchsten war. Hier sind aber nur Werte bis ende April vorhanden, daher ist dies mit vorsicht zu geniessen und für einen korrekten Vergleich müsste man bis Ende Jahr warten!
  • im 2019 die grösste Streuung der Maximalwerte vorhanden ist, wie schon oben erwähnt ist hier auch die höchste Windgeschwindigkeit überhaupt gemessen worden.
  • im Jahr 2017 der Median die höchste Maximalwindgeschwindigkeit zeigt, als dies in anderen Jahren der Fall ist (2020 einmal ausgenommen!)

Analyse summary-daily-subscribers_only-gender_grouped-citibike-tripweather.parquet

Hier werden ein paar Untersuchungen zur täglichen Zusammenfassung gemacht. Zu beachten ist, dass diese Zusammenfassung nur Informationen über Subscriber enthält und über alle Jahre gemacht wurde! Zusätzlich wurde auch eine Gruppierung nach Geschlecht vorgenommen.

In diesem Kapitel wird nicht mehr alles so detailliert beschrieben wie für den Oktober 2019!

Merkmale

In [91]:
df_sumsubs_bygen = pd.read_parquet(os.path.join(path, 'summary-daily-subscribers_only-gender_grouped-citibike-tripweather.parquet'))
In [92]:
df_sumsubs_bygen.tail().T
Out[92]:
Date 2020-04-29 2020-04-29 2020-04-30 2020-04-30 2020-04-30
Gender 1 2 0 1 2
Trip count 12383.00000 5180.00000 181.00000 8895.00000 3221.00000
Trip Duration mean 911.63708 1023.60927 968.38674 870.98954 976.06644
Trip Duration std 869.96530 877.27523 968.50382 887.19920 846.63441
Trip Duration min 61.00000 62.00000 108.00000 61.00000 61.00000
Trip Duration median 654.00000 789.50000 549.00000 609.00000 739.00000
Trip Duration max 16614.00000 16300.00000 6190.00000 19953.00000 13597.00000
Linear Distance mean 1.89932 1.99754 1.81687 1.72799 1.90184
Linear Distance std 1.78566 1.77725 1.92322 1.65931 1.69891
Linear Distance min 0.00000 0.00000 0.00000 0.00000 0.00000
Linear Distance median 1.35603 1.47473 1.08924 1.22591 1.40006
Linear Distance max 14.12451 12.30219 8.51553 16.66022 11.43502
Age 2020 count 12383.00000 5180.00000 181.00000 8895.00000 3221.00000
Age 2020 mean 40.72963 39.72220 44.00000 41.02530 39.40515
Age 2020 std 12.23206 12.04447 9.85788 12.20961 11.78659
Age 2020 min 16.00000 16.00000 24.00000 17.00000 17.00000
Age 2020 median 38.00000 36.00000 51.00000 39.00000 36.00000
Age 2020 max 80.00000 79.00000 70.00000 81.00000 77.00000
temp mean 10.79875 10.79875 11.83769 11.83769 11.83769
temp std 1.75832 1.75832 1.89143 1.89143 1.89143
temp median 10.74500 10.74500 10.82500 10.82500 10.82500
temp min 8.38000 8.38000 10.02000 10.02000 10.02000
temp max 14.69000 14.69000 15.11000 15.11000 15.11000
humidity mean 74.04167 74.04167 84.80769 84.80769 84.80769
humidity std 10.46518 10.46518 6.98008 6.98008 6.98008
humidity median 76.00000 76.00000 87.00000 87.00000 87.00000
humidity min 50 50 71 71 71
humidity max 87 87 93 93 93
wind_speed mean 4.74708 4.74708 6.13077 6.13077 6.13077
wind_speed std 2.19028 2.19028 1.59819 1.59819 1.59819
wind_speed median 4.85000 4.85000 6.20000 6.20000 6.20000
wind_speed min 1.50000 1.50000 3.10000 3.10000 3.10000
wind_speed max 8.70000 8.70000 8.70000 8.70000 8.70000
rain_1h sum 0.29000 0.29000 10.32000 10.32000 10.32000
rain_1h min 0.00000 0.00000 0.00000 0.00000 0.00000
rain_1h max 0.29000 0.29000 1.27000 1.27000 1.27000
snow_1h sum 0.00000 0.00000 0.00000 0.00000 0.00000
snow_1h min 0.00000 0.00000 0.00000 0.00000 0.00000
snow_1h max 0.00000 0.00000 0.00000 0.00000 0.00000

Zusätzlich zu den Merkmalen aus summary-daily-subscribers_only-gender_grouped-citibike-tripweather.parquet, gibt es noch zusätzlich das folgende Merkmal:

Statistische Einheit Merkmal Skalenniveau Beschreibung
User Gender Nominal Geschlecht des Benutzers: 0=unknown; 1=male; 2=female

Subscriber-Trips über alle Jahre nach Geschlecht

In [93]:
df_sumsubs_bygen.groupby(by='Gender')['Trip count'].describe().T
Out[93]:
Gender 0 1 2
count 2301.00000 2488.00000 2488.00000
mean 342.47110 25164.52613 8142.99759
std 410.36833 12576.74042 4472.32284
min 1.00000 762.00000 92.00000
25% 25.00000 15468.75000 4881.50000
50% 128.00000 24090.00000 7374.00000
75% 626.00000 33968.00000 11367.25000
max 1451.00000 59770.00000 21538.00000

Die Übersicht zeigt, dass

  • an 2'301 Tagen jemand gefahren ist, der sein Geschlecht nicht angegeben hat.
  • an 2'488 Tagen ein Mann und eine Frau ein Trip gefahren ist.
  • durchschnittlich 342.47 Trips pro Tag gefahren werden, von Benutzern die ihr Geschlecht nicht angeben.
  • durchschnittlich 25'164.53 Trips täglich von Männern gefahren werden, wohingegen durchschnittlich 8'143 Trips täglich von Frauen gefahren werden.
  • die Männer 59'770 Trips an ihrem stärksten Tag gefahren sind.
In [94]:
def get_data_trip_count_all_years():
    
    data = [
        df_sumsubs_bygen[df_sumsubs_bygen['Gender'] == '0']['Trip count'],
        df_sumsubs_bygen[df_sumsubs_bygen['Gender'] == '1']['Trip count'],
        df_sumsubs_bygen[df_sumsubs_bygen['Gender'] == '2']['Trip count']
    ]

    labels = [
        'Subscriber, unknown',
        'Subscriber, male',
        'Subscriber, female'
    ]
    
    return { 'data': data, 'labels': labels }
In [95]:
fig, ax = plt.subplots(figsize=(20, 5))

dict_data = get_data_trip_count_all_years()
_ = ax.boxplot(dict_data['data'], vert=False, showmeans=True, labels=dict_data['labels'], widths=0.8, showfliers=True)
ax.set_xlabel('Anzahl Trips')
ax.set_title('Tägliche Trips über alle Jahre')

ax.set_axisbelow(True)
ax.xaxis.grid(linestyle='dashed')

ax.get_xaxis().set_major_formatter(
    matplotlib.ticker.FuncFormatter(lambda x, p: format(int(x), ',')))

#plt.tight_layout()
plt.show()

Im Boxplot ist sofort ersichtlich das die Männer die meisten Trips gefahren sind. Sie weisen aber auch die höchste Streuung aus. Am wenigsten Trips wurden von Subscribern gemacht die ihr Geschlecht nicht angegeben haben.

In [96]:
fig = plt.figure(figsize=(20, 5))

ax = fig.add_subplot()

dict_data = get_data_trip_count_all_years()
for idx, data in enumerate(dict_data['data']):
    data_agg_monthly = data.groupby(pd.Grouper(freq='W')).sum()
    _ = ax.plot(data_agg_monthly.index, data_agg_monthly, label=dict_data['labels'][idx])

ax.set_xlabel('Jahr')
ax.set_ylabel('Anzahl Trips')
ax.set_title('Wöchentliche Anzahl Trips')

ax.yaxis.grid(linestyle='dashed')
ax.xaxis.grid(linestyle='dashed')

ax.ticklabel_format(axis='y', style='plain')

ax.get_yaxis().set_major_formatter(
    matplotlib.ticker.FuncFormatter(lambda x, p: format(int(x), ',')))

#ax.tick_params(axis='x', labelrotation=90)

plt.legend()
plt.show()

In obiger Grafik ist das gleiche Verhalten ersichtlich wie schon beschrieben. Hier ist auch ersichtlich das es einen steigenden Trend gibt und immer mehr Trips gefahren werden. Im 2020 scheint es aber einen stärkeren Einbruch zu geben, dies vermutlich aufgrund der weltweiten Pandemie.

Anhang

In [97]:
def numcols_hist(df):
    '''
    Plots all histograms of numeric columns in the DataFrame.
    '''    
    numeric_columns = []
    for col in df.columns:
        if df[col].dtype == np.float64 or df[col].dtype == np.int64 or isinstance(df[col].dtype, pd.Int64Dtype):
            numeric_columns.append(col)
    
    fig = plt.figure(figsize=(20, len(numeric_columns) * 2))
    gs = gridspec.GridSpec(len(numeric_columns), 2, figure=fig)

    for idx, col in enumerate(numeric_columns):
        ax = fig.add_subplot(gs[idx, 0])
        ax.set_title(col)
        _ = df[col].hist(ax=ax, bins=25)

        ax = fig.add_subplot(gs[idx, 1])
        ax.set_title(col + ' (log)')
        _ = df[col].hist(ax=ax, bins=25)
        ax.set_yscale('log')

    fig.tight_layout()
    plt.show()
In [98]:
def numcols_box(df):
    '''
    Plots all boxplots of numeric columns in the DataFrame.
    '''    
    numeric_columns = []
    for col in df.columns:
        if df[col].dtype == np.float64 or df[col].dtype == np.int64 or isinstance(df[col].dtype, pd.Int64Dtype):
            numeric_columns.append(col)
    
    fig = plt.figure(figsize=(20, len(numeric_columns) * 2))
    gs = gridspec.GridSpec(len(numeric_columns), 1, figure=fig)

    for idx, col in enumerate(numeric_columns):
        ax = fig.add_subplot(gs[idx, 0])
        ax.set_title(col)
        _ = ax.boxplot(df[col], vert=False, showmeans=True, widths=0.7)

    fig.tight_layout()
    plt.show()

samples_5000_201910-citibike-tripweather-data.parquet

In [99]:
df_oct2019_attachment = pd.read_parquet(os.path.join(path, 'samples_5000_201910-citibike-tripweather-data.parquet'))

Übersicht

In [100]:
df_oct2019_attachment.describe(include='all').T
Out[100]:
count unique top freq first last mean std min 25% 50% 75% max
dt_utc 5000 4995 2019-10-28 22:47:13+00:00 2 2019-10-01 04:48:07+00:00 2019-11-01 02:46:46+00:00 NaN NaN NaN NaN NaN NaN NaN
Trip Duration 5000.00000 NaN NaN NaN NaN NaN 922.72860 5575.16818 65.00000 362.00000 606.00000 1021.25000 377055.00000
Start Time 5000 4995 2019-10-28 18:47:13-04:00 2 2019-10-01 00:48:07-04:00 2019-10-31 22:46:46-04:00 NaN NaN NaN NaN NaN NaN NaN
Stop Time 5000 4989 2019-10-11 09:09:09-04:00 2 2019-10-01 01:01:40-04:00 2019-10-31 22:54:45-04:00 NaN NaN NaN NaN NaN NaN NaN
Start Station ID 5000.00000 NaN NaN NaN NaN NaN 1657.98500 1482.31215 72.00000 379.00000 508.00000 3292.00000 3881.00000
Start Station Name 5000 715 8 Ave & W 31 St 34 NaN NaN NaN NaN NaN NaN NaN NaN NaN
Start Station Latitude 5000.00000 NaN NaN NaN NaN NaN 40.73813 0.02969 40.65709 40.71910 40.73902 40.75763 40.81830
Start Station Longitude 5000.00000 NaN NaN NaN NaN NaN -73.98222 0.01951 -74.02535 -73.99530 -73.98518 -73.97189 -73.90774
End Station ID 5000.00000 NaN NaN NaN NaN NaN 1630.33940 1481.89614 72.00000 379.00000 507.50000 3301.00000 3881.00000
End Station Name 5000 714 Pershing Square North 50 NaN NaN NaN NaN NaN NaN NaN NaN NaN
End Station Latitude 5000.00000 NaN NaN NaN NaN NaN 40.73742 0.02940 40.65540 40.71850 40.73827 40.75643 40.81830
End Station Longitude 5000.00000 NaN NaN NaN NaN NaN -73.98255 0.01981 -74.02535 -73.99596 -73.98658 -73.97209 -73.90904
Bike ID 5000.00000 NaN NaN NaN NaN NaN 31481.20840 8364.20847 14536.00000 26023.00000 32546.00000 39384.25000 42046.00000
User Type 5000 2 Subscriber 4288 NaN NaN NaN NaN NaN NaN NaN NaN NaN
Birth Year 5000.00000 NaN NaN NaN NaN NaN 1980.38200 12.01451 1886.00000 1969.00000 1983.00000 1990.00000 2003.00000
Gender 5000 3 1 3359 NaN NaN NaN NaN NaN NaN NaN NaN NaN
Linear Distance 5000.00000 NaN NaN NaN NaN NaN 1.75303 1.36817 0.00000 0.83659 1.34903 2.24275 11.32096
Age 2020 5000.00000 NaN NaN NaN NaN NaN 39.61800 12.01451 17.00000 30.00000 37.00000 51.00000 134.00000
temp 5000.00000 NaN NaN NaN NaN NaN 16.61370 3.98866 6.09000 14.06000 16.02000 18.41000 33.07000
pressure 5000.00000 NaN NaN NaN NaN NaN 1018.46120 6.82257 988.00000 1015.00000 1019.00000 1023.00000 1032.00000
humidity 5000.00000 NaN NaN NaN NaN NaN 68.55320 12.63561 43.00000 59.00000 67.00000 77.00000 99.00000
wind_speed 5000.00000 NaN NaN NaN NaN NaN 5.93327 2.99727 0.64000 3.66000 5.68000 8.17000 18.91000
rain_1h 5000.00000 NaN NaN NaN NaN NaN 0.08230 0.42080 0.00000 0.00000 0.00000 0.00000 7.81000
snow_1h 5000.00000 NaN NaN NaN NaN NaN 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000
clouds_all 5000.00000 NaN NaN NaN NaN NaN 26.11940 41.28650 0.00000 0.00000 0.00000 67.00000 100.00000
weather_id 5000.00000 NaN NaN NaN NaN NaN 727.22980 129.39356 500.00000 800.00000 800.00000 800.00000 804.00000
weather_main 5000 3 Clear 2744 NaN NaN NaN NaN NaN NaN NaN NaN NaN
weather_description 5000 8 sky is clear 2744 NaN NaN NaN NaN NaN NaN NaN NaN NaN

Histogramme für alle numerischen Merkmale

In [101]:
%%time
numcols_hist(df_oct2019_attachment)
CPU times: user 4.61 s, sys: 135 ms, total: 4.74 s
Wall time: 4.59 s

Boxplots für alle numerischen Merkmale

In [102]:
%%time
numcols_box(df_oct2019_attachment)
CPU times: user 1.27 s, sys: 135 ms, total: 1.4 s
Wall time: 1.25 s

Scatterplot Matrix

Es sind sehr viele Merkmale! Daher dauert die Berechnung auf nicht performanten Rechnern einige Zeit!

Es werden aufgrund der Performance und der Übersichtlichkeit hier nur einige Merkmale in den Plot mit einbezogen!

Auch werden nur Trips mit einer Dauer von maximal 33.1 Minuten berücksichtigt und auch nur eine zufällige Stichprobe von 500 Beobachtungen dargestellt.

In [103]:
number_of_samples = 500
In [104]:
df_oct2019_attachment.columns
Out[104]:
Index(['dt_utc', 'Trip Duration', 'Start Time', 'Stop Time',
       'Start Station ID', 'Start Station Name', 'Start Station Latitude',
       'Start Station Longitude', 'End Station ID', 'End Station Name',
       'End Station Latitude', 'End Station Longitude', 'Bike ID', 'User Type',
       'Birth Year', 'Gender', 'Linear Distance', 'Age 2020', 'temp',
       'pressure', 'humidity', 'wind_speed', 'rain_1h', 'snow_1h',
       'clouds_all', 'weather_id', 'weather_main', 'weather_description'],
      dtype='object')
In [105]:
columns = ['Trip Duration', 'Age 2020', 'temp', 'wind_speed', 'rain_1h']
In [106]:
%%time
_ = sns.pairplot(df_oct2019_attachment[df_oct2019_attachment['Trip Duration'] <= 1*60*33.1][columns].sample(number_of_samples, random_state=SEED), height=3)
plt.show()
CPU times: user 3.28 s, sys: 497 ms, total: 3.78 s
Wall time: 3.08 s

Auf den ersten Blick sind hier nirgendwo spezielle Zusammenhänge zu erkennen.

summary-daily-subscribers_only-citibike-tripweather.parquet

In [107]:
df_sumsubs_attachment = pd.read_parquet(os.path.join(path, 'summary-daily-subscribers_only-citibike-tripweather.parquet'))

Übersicht

In [108]:
df_sumsubs_attachment.describe(include='all').T
Out[108]:
count mean std min 25% 50% 75% max
Trip count 2488.00000 33624.25442 17238.81747 854.00000 20760.00000 31608.00000 45691.25000 82687.00000
Trip Duration mean 2488.00000 730.18025 84.82187 528.94266 666.54312 730.45161 776.26491 1362.62602
Trip Duration std 2488.00000 644.92487 100.00514 425.17287 579.20245 626.70581 693.31063 1295.39725
Trip Duration min 2488.00000 60.77492 0.60952 60.00000 60.00000 61.00000 61.00000 68.00000
Trip Duration median 2488.00000 564.98734 63.83065 384.00000 515.00000 569.00000 604.00000 1146.00000
Trip Duration max 2488.00000 19473.83079 2152.79880 4386.00000 18590.25000 20168.00000 21041.25000 21599.00000
Linear Distance mean 2488.00000 1.69977 0.15094 1.18932 1.59777 1.71382 1.81063 2.35995
Linear Distance std 2488.00000 1.29998 0.15181 0.81756 1.20612 1.29468 1.41221 1.92825
Linear Distance min 2488.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000
Linear Distance median 2488.00000 1.32993 0.10533 0.94019 1.25917 1.34245 1.40088 1.89549
Linear Distance max 2488.00000 11.30068 1.99914 6.19236 9.60346 11.25219 12.72245 29.25823
Age 2020 count 2488.00000 33624.25442 17238.81747 854.00000 20760.00000 31608.00000 45691.25000 82687.00000
Age 2020 mean 2488.00000 42.01439 1.89851 37.63548 40.47882 41.97557 43.61358 46.63874
Age 2020 std 2488.00000 11.57191 0.42395 10.11158 11.32096 11.58466 11.85519 13.05243
Age 2020 min 2488.00000 19.77854 1.96746 16.00000 18.00000 20.00000 21.00000 23.00000
Age 2020 median 2488.00000 39.47749 2.31447 34.00000 38.00000 39.00000 41.00000 45.00000
Age 2020 max 2488.00000 80.88384 0.36725 76.00000 81.00000 81.00000 81.00000 81.00000
temp mean 2488.00000 13.11709 9.67349 -13.26792 5.45958 13.18312 22.11437 32.21250
temp std 2488.00000 2.47641 1.04339 0.23220 1.73672 2.41450 3.09786 6.92038
temp median 2488.00000 12.61138 9.72265 -14.05000 4.83000 12.73750 21.76125 32.47500
temp min 2488.00000 9.83856 9.45862 -17.28000 2.42750 9.83500 18.46500 27.75000
temp max 2488.00000 17.21666 9.94479 -9.47000 9.08750 17.66000 26.33000 36.44000
humidity mean 2488.00000 62.95264 14.79839 21.54167 51.58173 62.81250 74.33333 97.72000
humidity std 2488.00000 10.56184 4.78186 1.01419 7.07555 9.81583 13.19837 31.44601
humidity median 2488.00000 64.51407 16.28417 20.50000 52.00000 64.00000 78.00000 100.00000
humidity min 2488.00000 45.29542 15.48926 8.00000 34.00000 44.00000 55.00000 94.00000
humidity max 2488.00000 78.21704 14.31669 31.00000 67.00000 81.00000 91.00000 100.00000
wind_speed mean 2488.00000 3.19217 1.33719 1.21583 2.34438 2.85833 3.64031 14.42750
wind_speed std 2488.00000 1.12257 0.50754 0.23324 0.77431 1.00085 1.35089 4.00292
wind_speed median 2488.00000 3.07145 1.36614 1.06000 2.10000 2.60000 3.60000 14.13000
wind_speed min 2488.00000 1.47889 0.92916 0.01000 1.02000 1.50000 1.50000 11.13000
wind_speed max 2488.00000 5.54780 2.12569 1.75000 4.10000 5.10000 6.70000 18.91000
rain_1h sum 2488.00000 4.28425 9.60217 0.00000 0.00000 0.20000 3.64000 110.02000
rain_1h min 2488.00000 0.00071 0.01281 0.00000 0.00000 0.00000 0.00000 0.30000
rain_1h max 2488.00000 1.10980 2.38371 0.00000 0.00000 0.20000 1.20000 32.77000
snow_1h sum 2488.00000 0.94801 17.89780 0.00000 0.00000 0.00000 0.00000 857.76000
snow_1h min 2488.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000
snow_1h max 2488.00000 0.17759 2.40980 0.00000 0.00000 0.00000 0.00000 94.70000

Histogramme für alle numerischen Merkmale

In [109]:
%%time
numcols_hist(df_sumsubs_attachment)
CPU times: user 9.16 s, sys: 160 ms, total: 9.32 s
Wall time: 9.17 s

Boxplots für alle numerischen Merkmale

In [110]:
%%time
numcols_box(df_sumsubs_attachment)
CPU times: user 2.36 s, sys: 148 ms, total: 2.51 s
Wall time: 2.35 s

Scatterplot Matrix

Es sind sehr viele Merkmale! Daher dauert die Berechnung auf nicht performanten Rechnern einige Zeit!

Es werden aufgrund der Performance und der Übersichtlichkeit hier nur einige Merkmale in den Plot mit einbezogen!

In [111]:
df_sumsubs_attachment.columns
Out[111]:
Index(['Trip count', 'Trip Duration mean', 'Trip Duration std',
       'Trip Duration min', 'Trip Duration median', 'Trip Duration max',
       'Linear Distance mean', 'Linear Distance std', 'Linear Distance min',
       'Linear Distance median', 'Linear Distance max', 'Age 2020 count',
       'Age 2020 mean', 'Age 2020 std', 'Age 2020 min', 'Age 2020 median',
       'Age 2020 max', 'temp mean', 'temp std', 'temp median', 'temp min',
       'temp max', 'humidity mean', 'humidity std', 'humidity median',
       'humidity min', 'humidity max', 'wind_speed mean', 'wind_speed std',
       'wind_speed median', 'wind_speed min', 'wind_speed max', 'rain_1h sum',
       'rain_1h min', 'rain_1h max', 'snow_1h sum', 'snow_1h min',
       'snow_1h max'],
      dtype='object')
In [112]:
columns = ['Trip count', 'Trip Duration mean', 'temp mean', 'wind_speed mean', 'rain_1h sum', 'snow_1h sum']
In [113]:
%%time
_ = sns.pairplot(df_sumsubs_attachment[columns], height=3)
plt.show()
CPU times: user 4.89 s, sys: 701 ms, total: 5.59 s
Wall time: 4.67 s

Es scheint das z.B. bei 'temp mean' und 'Trip Duration mean' ein scheinbarer, linearer Zusammenhang bestehen könnte.

summary-daily-subscribers_only-gender_grouped-citibike-tripweather.parquet

In [114]:
df_sumsubs_bygen_attachment = pd.read_parquet(os.path.join(path, 'summary-daily-subscribers_only-gender_grouped-citibike-tripweather.parquet'))

Übersicht

In [115]:
df_sumsubs_bygen_attachment.describe(include='all').T
Out[115]:
count unique top freq mean std min 25% 50% 75% max
Gender 7277 3 2 2488 NaN NaN NaN NaN NaN NaN NaN
Trip count 7277.00000 NaN NaN NaN 11496.10348 12962.49726 1.00000 684.00000 7161.00000 17065.00000 59770.00000
Trip Duration mean 7277.00000 NaN NaN NaN 757.97679 244.26912 85.00000 672.89272 740.46329 826.53929 10395.50000
Trip Duration std 7277.00000 NaN NaN NaN 658.46979 283.66226 0.00000 557.47129 627.28950 724.25046 6646.64319
Trip Duration min 7277.00000 NaN NaN NaN 91.73066 151.13083 60.00000 61.00000 61.00000 67.00000 5386.00000
Trip Duration median 7277.00000 NaN NaN NaN 590.87928 267.98768 85.00000 518.00000 572.50000 639.00000 12106.50000
Trip Duration max 7277.00000 NaN NaN NaN 13947.01731 6935.26159 85.00000 8076.00000 16674.00000 19802.00000 21599.00000
Linear Distance mean 7277.00000 NaN NaN NaN 1.68515 0.29682 0.00000 1.57479 1.71173 1.82666 7.72221
Linear Distance std 7277.00000 NaN NaN NaN 1.26070 0.29663 0.00000 1.17874 1.29578 1.42450 4.23115
Linear Distance min 7277.00000 NaN NaN NaN 0.06476 0.29353 0.00000 0.00000 0.00000 0.00000 7.72221
Linear Distance median 7277.00000 NaN NaN NaN 1.33087 0.27694 0.00000 1.23672 1.33741 1.42586 7.72221
Linear Distance max 7277.00000 NaN NaN NaN 9.41702 3.05602 0.00000 8.08143 9.64450 11.55620 29.25823
Age 2020 count 7277.00000 NaN NaN NaN 11496.10348 12962.49726 1.00000 684.00000 7161.00000 17065.00000 59770.00000
Age 2020 mean 7277.00000 NaN NaN NaN 41.84666 5.20987 24.00000 40.10377 41.97368 43.95850 69.00000
Age 2020 std 7277.00000 NaN NaN NaN 10.48747 2.86389 0.00000 10.40808 11.34530 11.75382 31.81981
Age 2020 min 7277.00000 NaN NaN NaN 21.38615 6.15570 16.00000 19.00000 20.00000 22.00000 69.00000
Age 2020 median 7277.00000 NaN NaN NaN 40.35798 6.43786 24.00000 37.00000 39.00000 42.00000 69.00000
Age 2020 max 7277.00000 NaN NaN NaN 75.62416 11.92847 24.00000 77.00000 80.00000 81.00000 81.00000
temp mean 7277.00000 NaN NaN NaN 13.26330 9.59753 -13.26792 5.61750 13.46292 22.16250 32.21250
temp std 7277.00000 NaN NaN NaN 2.47836 1.04115 0.23220 1.73834 2.41652 3.10292 6.92038
temp median 7277.00000 NaN NaN NaN 12.75754 9.64447 -14.05000 5.04500 12.94500 21.84500 32.47500
temp min 7277.00000 NaN NaN NaN 9.98279 9.38138 -17.28000 2.64000 10.00000 18.50000 27.75000
temp max 7277.00000 NaN NaN NaN 17.36718 9.87168 -9.47000 9.24000 17.89000 26.46000 36.44000
humidity mean 7277.00000 NaN NaN NaN 63.06682 14.76252 21.54167 51.75000 62.95833 74.41667 97.72000
humidity std 7277.00000 NaN NaN NaN 10.56452 4.77441 1.01419 7.08016 9.81680 13.17325 31.44601
humidity median 7277.00000 NaN NaN NaN 64.64216 16.23476 20.50000 52.00000 64.00000 78.00000 100.00000
humidity min 7277.00000 NaN NaN NaN 45.39920 15.49780 8.00000 34.00000 44.00000 55.00000 94.00000
humidity max 7277.00000 NaN NaN NaN 78.33187 14.26562 31.00000 68.00000 81.00000 91.00000 100.00000
wind_speed mean 7277.00000 NaN NaN NaN 3.19492 1.34190 1.21583 2.34458 2.85958 3.64292 14.42750
wind_speed std 7277.00000 NaN NaN NaN 1.12299 0.50977 0.23324 0.77350 0.99979 1.35072 4.00292
wind_speed median 7277.00000 NaN NaN NaN 3.07417 1.37085 1.06000 2.10000 2.60000 3.60000 14.13000
wind_speed min 7277.00000 NaN NaN NaN 1.48131 0.93428 0.01000 1.02000 1.50000 1.50000 11.13000
wind_speed max 7277.00000 NaN NaN NaN 5.54928 2.13022 1.75000 4.10000 5.10000 6.70000 18.91000
rain_1h sum 7277.00000 NaN NaN NaN 4.26730 9.50955 0.00000 0.00000 0.20000 3.69000 110.02000
rain_1h min 7277.00000 NaN NaN NaN 0.00073 0.01297 0.00000 0.00000 0.00000 0.00000 0.30000
rain_1h max 7277.00000 NaN NaN NaN 1.11808 2.39440 0.00000 0.00000 0.20000 1.20000 32.77000
snow_1h sum 7277.00000 NaN NaN NaN 0.90369 18.04656 0.00000 0.00000 0.00000 0.00000 857.76000
snow_1h min 7277.00000 NaN NaN NaN 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000
snow_1h max 7277.00000 NaN NaN NaN 0.17301 2.42887 0.00000 0.00000 0.00000 0.00000 94.70000

Histogramme für alle numerischen Merkmale

In [116]:
%%time
numcols_hist(df_sumsubs_bygen_attachment)
CPU times: user 8.81 s, sys: 130 ms, total: 8.94 s
Wall time: 8.79 s

Boxplots für alle numerischen Merkmale

In [117]:
%%time
numcols_box(df_sumsubs_bygen_attachment)
CPU times: user 2.86 s, sys: 116 ms, total: 2.98 s
Wall time: 2.82 s

Scatterplot Matrix

Es sind sehr viele Merkmale! Daher dauert die Berechnung auf nicht performanten Rechnern einige Zeit!

Es werden aufgrund der Performance und der Übersichtlichkeit hier nur einige Merkmale in den Plot mit einbezogen!

Auch werden immer nur 250 zufällig gewählte Stichproben dargestellt!

In [118]:
number_of_samples = 250

Scatterplot Matrix mit Geschlecht

In [119]:
columns = ['Gender', 'Trip count', 'Trip Duration mean', 'temp mean', 'wind_speed mean', 'rain_1h sum', 'snow_1h sum']
In [120]:
%%time
_ = sns.pairplot(df_sumsubs_bygen_attachment[columns].sample(number_of_samples), height=3, hue='Gender')
plt.show()
/home/iimsand/.virtualenvs/ffhs/semarb-statda/lib/python3.8/site-packages/seaborn/distributions.py:369: UserWarning: Default bandwidth for data is 0; skipping density estimation.
  warnings.warn(msg, UserWarning)
/home/iimsand/.virtualenvs/ffhs/semarb-statda/lib/python3.8/site-packages/seaborn/distributions.py:369: UserWarning: Default bandwidth for data is 0; skipping density estimation.
  warnings.warn(msg, UserWarning)
/home/iimsand/.virtualenvs/ffhs/semarb-statda/lib/python3.8/site-packages/seaborn/distributions.py:369: UserWarning: Default bandwidth for data is 0; skipping density estimation.
  warnings.warn(msg, UserWarning)
CPU times: user 6.19 s, sys: 1.45 s, total: 7.64 s
Wall time: 5.63 s

Man sieht, dass die Beobachtungen mit fehlendem Geschlecht (0, unknown) die Plots etwas unübersichtlich machen und Konzentrationen entstehen. Die Werte sind quasi Ausreisser. Daher wird nochmal eine Scatterplot Matrix erstellt ohne 0, 'unknown'.

Scatterplot Matrix mit Geschlecht, aber ohne 0 (unknown)

In [121]:
%%time
_ = sns.pairplot(df_sumsubs_bygen_attachment[columns]
        [(df_sumsubs_bygen_attachment['Gender'] == '1') | (df_sumsubs_bygen_attachment['Gender'] == '2')]
        .sample(number_of_samples, random_state=SEED), height=3, hue='Gender')
plt.show()
/home/iimsand/.virtualenvs/ffhs/semarb-statda/lib/python3.8/site-packages/seaborn/distributions.py:369: UserWarning: Default bandwidth for data is 0; skipping density estimation.
  warnings.warn(msg, UserWarning)
/home/iimsand/.virtualenvs/ffhs/semarb-statda/lib/python3.8/site-packages/seaborn/distributions.py:369: UserWarning: Default bandwidth for data is 0; skipping density estimation.
  warnings.warn(msg, UserWarning)
CPU times: user 6.1 s, sys: 1.29 s, total: 7.39 s
Wall time: 5.69 s

Hier fällt auf, dass es einen linearen Zusammenhang zu geben scheint zwischen 'Trip count' und 'temp mean' der bei den Frauen ausgeprägter ist als bei den Männern. Auch 'Trip Duration mean' und 'temp mean' scheinen einen Zusammenhang aufzuweisen.

In [ ]: